~bigbes/agents-dev · events

a3dprr05obih2ba26s31mjd0v5lm54jp · 469 rows

idissue_idevent_typeactorold_valuenew_valuecommentcreated_at
019f58ae-407c-7999-875f-11c78322b49eah-nylcreatedEugene BlikhNULL2026-07-13T02:34:02Z
019f58ae-43b8-7683-a96f-8e547b31ec37ah-nyl.1createdEugene BlikhNULL2026-07-13T02:34:03Z
019f58ae-b83d-752c-8b26-9aba0aaeb237ah-nyl.2createdEugene BlikhNULL2026-07-13T02:34:33Z
019f58ae-bbf8-7b85-a2f8-a341f3fc6d9dah-nyl.3createdEugene BlikhNULL2026-07-13T02:34:34Z
019f58af-5f36-73b9-b949-03c66a15432aah-nyl.4createdEugene BlikhNULL2026-07-13T02:35:16Z
019f58af-62ab-7a9e-b2a4-6d11ed68d18bah-nyl.5createdEugene BlikhNULL2026-07-13T02:35:16Z
019f58af-f5f5-7403-90eb-db4c754742f1ah-nyl.6createdEugene BlikhNULL2026-07-13T02:35:54Z
019f58af-fad9-748c-a6a1-71948b188bccah-nyl.7createdEugene BlikhNULL2026-07-13T02:35:55Z
019f58af-fe8c-793b-8700-9ce02148b86fah-nyl.8createdEugene BlikhNULL2026-07-13T02:35:56Z
019f58b0-7816-7c6b-a36d-bbbd7f5b5eb4ah-xuccreatedEugene BlikhNULL2026-07-13T02:36:27Z
019f58b0-7a39-74fd-ae5f-365c75d5aab6ah-ydxcreatedEugene BlikhNULL2026-07-13T02:36:28Z
019f58b0-7c89-7f0f-b898-f324000256f4ah-0gecreatedEugene BlikhNULL2026-07-13T02:36:29Z
019f58b0-7e71-72b3-9040-55429df727a2ah-oeqcreatedEugene BlikhNULL2026-07-13T02:36:29Z
019f58b0-806b-71d0-9c76-31cf450cbe41ah-k23createdEugene BlikhNULL2026-07-13T02:36:30Z
019f58b2-07e1-78e5-81fa-b80c23cd0964ah-nyl.1status_changedEugene Blikh{"id":"ah-nyl.1","title":"foundation: go module, domain, ports, config","description":"Create the Go foundation of agents-dev exactly per docs/SPEC.md (read it fully first; SS4-6, SS12-13 are normative for this task).\n\nDeliverables:\n1. go.mod: module go.bigb.es/agents-dev, go 1.26. Add ALL stage-1 external deps now so siblings never touch go.mod: modernc.org/sqlite, gopkg.in/yaml.v3. Populate go.sum by building a throwaway smoke import (e.g. internal/smoke_test.go importing both, then delete the file but keep go.sum entries; or keep a tiny blank-import file under internal/deps/deps.go with build tag 'deps' — your choice, document it).\n2. internal/domain: types + constants from SPEC SS5 (State, Outcome, Task, Repo, TaskRecord, Run, Event, RunSummary) and pure transition helpers, at minimum CanClaim(*TaskRecord) bool per SS5 semantics. Table tests for every helper.\n3. internal/ports: interfaces exactly as SPEC SS6 (Board, Runner, Store, Notifier + BoardTask, StartSpec, RunInfo, RunStatus). Doc comments on every method stating error/nil semantics ((nil, nil) for absent, etc.).\n4. internal/config: Load(path string) (*Config, error) implementing SPEC SS12: yaml.v3 with KnownFields(true), ${VAR} env expansion in string values (only for vars that exist; unknown var = validation error), \"~\" expansion in paths, duration parsing, validation that collects ALL problems into one error (repos non-empty, default_repo/default_role resolve, buckets map has all six canonical keys, listen is loopback host:port). Config struct mirrors config.example.yaml at repo root (keep the two in sync; fix the example if you find an inconsistency and note it in the commit message). Tests: golden-load of config.example.yaml with env set, plus failure cases.\n\nRules: stdlib + the two deps only; log/slog if logging is needed (probably not here); no package-level state. Run: go build ./... \u0026\u0026 go vet ./... \u0026\u0026 go test ./... (allowed for THIS task only, since you own the whole tree). Commit everything as one or two commits, message style 'domain: ...', 'config: ...'. Do NOT push. Do NOT touch .beads/.\n\nAcceptance: go build/vet/test green on a clean checkout; interfaces compile exactly against the names/signatures in SPEC SS6 (parallel siblings will implement them verbatim); config.example.yaml loads.\n","status":"open","priority":0,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:34:03Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:34:03Z"}{"status":"in_progress"}NULL2026-07-13T02:38:10Z
019f58c1-7bc1-7589-bf55-7eba737f89bfah-nyl.1closedEugene BlikhClosedNULL2026-07-13T02:55:02Z
019f58c1-7e91-7947-accb-8220e4be6b23ah-nyl.2status_changedEugene Blikh{"id":"ah-nyl.2","title":"store: SQLite implementation of ports.Store","description":"Implement internal/store per docs/SPEC.md SS7 (read SPEC fully; SS5-7 normative). ports.Store on modernc.org/sqlite.\n\nDeliverables:\n- internal/store/store.go: New(path string) (*Store, error) — opens DB, applies PRAGMAs (WAL, busy_timeout=5000, foreign_keys=on), runs migrations; Close().\n- Migrations: embedded schema.sql (embed package), applied under a PRAGMA user_version gate (hand-rolled, target version 1). DDL exactly per SPEC SS7.\n- All ports.Store methods with the documented semantics: GetTask/LatestRun return (nil, nil) when absent; CreateRun surfaces UNIQUE(task_id, attempt) violation as a distinguishable error (exported sentinel ErrDuplicateRun); UpsertTask insert-or-update by id; timestamps RFC3339 UTC; contexts honored (database/sql *Context variants everywhere).\n- Compile-time check: var _ ports.Store = (*Store)(nil).\n\nTests (stdlib testing only, t.TempDir() databases): round-trip every method; absent-row nil,nil; duplicate run -\u003e ErrDuplicateRun; upsert updates fields + updated_at; ListTasks ordering deterministic (by id); events append + monotonically increasing seq; migration idempotence (New twice on same file).\n\nConstraints: work ONLY under internal/store/. Do not modify go.mod/go.sum (deps are already there), other packages, or .beads/. Build/test ONLY your package: go build ./internal/store/... \u0026\u0026 go vet ./internal/store/... \u0026\u0026 go test ./internal/store/... . Commit with 'store: ...' staging only internal/store. Do NOT push.\n","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:34:33Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:34:33Z"}{"status":"in_progress"}NULL2026-07-13T02:55:03Z
019f58c1-8017-7207-9931-05352f4e49b1ah-nyl.3status_changedEugene Blikh{"id":"ah-nyl.3","title":"vikunja: Board adapter over the REST API","description":"Implement internal/vikunja per docs/SPEC.md SS8 (read SPEC fully; SS6, SS8 normative). ports.Board over the Vikunja 2.3.0 REST API.\n\nDeliverables:\n- New(cfg config.Vikunja, logger *slog.Logger) (*Client, error) storing an http.Client with a sane timeout (~15s).\n- Bucket resolution per SPEC SS8: locate the kanban view of the configured project, build title-\u003ebucketID and bucketID-\u003ecanonical-name maps from the config buckets mapping; missing configured title = error listing found titles. Resolve lazily on first use and cache; provide a Refresh path when a lookup misses (board edited).\n- Snapshot(ctx): tasks of the project with canonical bucket names (\"\" for unmapped buckets), honoring pagination.\n- MoveToBucket(ctx, taskID, canonical), Comment(ctx, taskID, markdown), each per SPEC SS8 endpoints.\n- Error style: non-2xx -\u003e error with method, path, status, and \u003c=200 bytes of body.\n- Compile-time check: var _ ports.Board = (*Client)(nil).\n\nIMPORTANT — verify the real API contract before coding: the live OpenAPI JSON is at https://tasks.bigb.es/api/v1/docs.json (Swagger UI at /api/v1/docs) — fetch it (no auth needed for the spec itself) and confirm exact paths, request/response shapes, and pagination headers for: project views list, kanban view tasks, buckets list, bucket task move, comment create (Vikunja uses PUT-for-create), single task get. If the live spec is unreachable, use the upstream docs at https://vikunja.io/docs/ and pin your best understanding in code comments + fixtures. SPEC SS8's endpoint list is the expected shape, not gospel — trust the OpenAPI.\n\nTests: httptest.Server fixtures (JSON canned from the OpenAPI shapes) covering snapshot incl. pagination + unmapped buckets, bucket resolution failure (helpful error), move, comment, non-2xx error rendering. No live-network tests.\n\nConstraints: work ONLY under internal/vikunja/. No go.mod changes, no other packages, no .beads/. Build/test ONLY your package (go build/vet/test ./internal/vikunja/...). Commit 'vikunja: ...' staging only internal/vikunja. Do NOT push.\n","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:34:34Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:34:34Z"}{"status":"in_progress"}NULL2026-07-13T02:55:04Z
019f58c1-8183-729f-af88-6eec7c4ef330ah-nyl.4status_changedEugene Blikh{"id":"ah-nyl.4","title":"runner: pi+zellij implementation + ahub-run supervisor","description":"Implement internal/runner + cmd/ahub-run per docs/SPEC.md SS9 (read SPEC fully; SS6, SS9 normative). This is the trickiest package — the SS9 contract (worktree layout, .task/ files, status precedence) is normative; follow it to the letter.\n\nDeliverables:\n- internal/runner: New(cfg *config.Config, logger *slog.Logger) *PiZellij implementing ports.Runner (compile-time check var _ ports.Runner = ...).\n - Start: create/reuse worktree + branch per SS9 (git -C \u003crepo\u003e worktree add ...; handle existing worktree dir and existing branch for retries), write .task/PROMPT.md and .task/meta.json, worktree-local ignore via the resolved git-dir info/exclude per SS9, ensure zellij session (zellij attach --create-background task-\u003cid\u003e), spawn the pane (zellij --session task-\u003cid\u003e run --cwd \u003cworktree\u003e -- ahub-run --task-id N --attempt K --report-url \u003curl\u003e -- pi --mode json -p @.task/PROMPT.md --model \u003cmodel\u003e --no-skills --no-extensions [pi_args...]). Return RunInfo.\n - Status: precedence per SS9 — exit.json =\u003e Completed (outcome success/error by code); else session alive in `zellij list-sessions --short` =\u003e Running with LastEvent=mtime(events.jsonl); else Completed with OutcomeCrashed, exit -1. Parse PiSession + CostUSD from events.jsonl leniently: scan lines as loose JSON maps; session id from the first object that has a plausible session identifier; cost accumulated from usage/cost fields when present. IMPORTANT: pi 0.70.2 is installed locally — empirically capture a real `pi --mode json -p 'say hi'` JSONL sample (any cheap/configured model, or ask for the shape via `pi --help` + a dry attempt; if no model is invocable offline, mark the parser 'best-effort, fixture-based' and derive fixtures from pi's documented event shape), commit the sample as a testdata fixture, and pin the parser to it.\n - Kill: zellij kill-session + best-effort delete-session. Summary: git log/diff per SS9.\n - ALL zellij/pi/git argv construction centralized in commands.go with unit tests asserting exact argv (SS9 requirement).\n- cmd/ahub-run: supervisor per SS9 contract — tee child stdout to .task/events.jsonl (stderr passthrough), atomic exit.json (tmp+rename), best-effort POST to --report-url (2s timeout, 1 retry), signal-death -\u003e 128+sig, --keep-pane default true iff $ZELLIJ set (then print resume hint + exec $SHELL), false =\u003e exit with child code.\n\nTests: stub `zellij`/`pi`/(where sensible `git` is real — use real git with a t.TempDir() repo for worktree tests) as executable scripts prepended to PATH; cover: worktree create+reuse, argv construction, status precedence matrix (exit.json / alive / gone), events parsing from fixture, ahub-run end-to-end via os/exec (tee, atomic exit.json, report POST to httptest, exit-code mapping). No sleeps \u003e100ms; no real zellij sessions in tests.\n\nConstraints: work ONLY under internal/runner/ and cmd/ahub-run/. No go.mod changes, no other packages, no .beads/. Build/test ONLY yours: go build ./internal/runner/... ./cmd/ahub-run/... \u0026\u0026 go vet \u003csame\u003e \u0026\u0026 go test \u003csame\u003e. Commit 'runner: ...' staging only your paths. Do NOT push.\n","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:35:16Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:35:16Z"}{"status":"in_progress"}NULL2026-07-13T02:55:04Z
019f58c1-82f6-7835-84f8-e7bdd4c477a5ah-nyl.5status_changedEugene Blikh{"id":"ah-nyl.5","title":"reconcile: the control loop","description":"Implement internal/reconcile per docs/SPEC.md SS10 (read SPEC fully; SS5, SS6, SS10, SS12 normative). The reconciler imports ONLY internal/domain, internal/ports, internal/config (+ stdlib). Sibling packages (store/vikunja/runner) may not compile yet — you must not import or build them.\n\nDeliverables:\n- New(deps Deps) *Reconciler where Deps{Store ports.Store; Board ports.Board; Runner ports.Runner; Notifier ports.Notifier; Cfg *config.Config; Log *slog.Logger; Now func() time.Time}.\n- Run(ctx): loop — iterate every cfg.PollInterval, plus immediately when poked; Poke() (non-blocking, coalescing via 1-buffered channel); clean shutdown on ctx cancel.\n- iterate(ctx) implementing SPEC SS10 verbatim: the bucket switch (ready claim/heal; in_progress adopt-or-fail/check; terminal buckets kill+align), vanish handling for store tasks missing from snapshot, claim ordering (persist BEFORE moving the card), check() with finalize on Completed, timeout kill (Now() - StartedAt \u003e cfg.Timeout -\u003e Kill + OutcomeTimeout), comments per the SS10 templates (claim comment with attach hint; success comment with DiffStat+Commits; failure comment with outcome + fenced tail of events — obtain the tail via Runner.Summary? No: events tail is runner-internal; include what RunStatus/Summary give you: outcome, exit code, diff stat if any, and reference to the worktree path + zellij attach hint. Keep comment builders as small pure funcs with tests).\n- Prompt rendering per SPEC SS12: text/template over the role prompt file with {ID, Title, Description, Branch, RepoSlug}; render errors -\u003e comment + move to failed (per SS12).\n- Per-task action errors: log, append event where sensible, continue with other tasks; Snapshot error aborts the iteration (SS10).\n- Every state-changing action appends a domain.Event via Store.AppendEvent.\n\nTests (the heart of this task — table-driven, in-memory fakes for all four ports, fake clock): claim happy path (verify order: CreateRun+UpsertTask precede MoveToBucket; comment posted), exit-0 finalize -\u003e in_review + summary comment, exit!=0 -\u003e failed + diagnostic comment, timeout -\u003e Kill + failed(timeout), human drag to cancelled mid-run -\u003e Kill + killed + aligned, crash-between-persist-and-move heal (rec in_progress + bucket ready -\u003e just MoveToBucket), adopt (bucket in_progress, no rec, runner reports running), adopt-fail (no runtime -\u003e failed + comment), vanish (in store, not on board -\u003e kill + cancelled), poke triggers immediate iteration, ctx cancel stops Run. Fakes record calls for assertion; no real time.Sleep beyond trivial.\n\nConstraints: work ONLY under internal/reconcile/. No go.mod changes, no .beads/. Build/test ONLY: go build ./internal/reconcile/... \u0026\u0026 go vet ./internal/reconcile/... \u0026\u0026 go test ./internal/reconcile/... . Commit 'reconcile: ...' staging only internal/reconcile. Do NOT push.\n","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:35:17Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:35:17Z"}{"status":"in_progress"}NULL2026-07-13T02:55:04Z
019f58ce-c5e4-77fe-a553-0daf95ce21f3ah-nyl.8closedEugene Blikhsuperseded: auxilia + testify adopted as baseline conventions before wave 2 (user directive); refit of landed packages tracked in a dedicated beadNULL2026-07-13T03:09:33Z
019f58ce-c818-7a21-976c-ef60c7a0e4d5ah-nyl.6updatedEugene Blikh{"id":"ah-nyl.6","title":"wiring: httpapi + agenthubd + ahub CLIs","description":"Wire the daemon together per docs/SPEC.md SS3, SS11 (read SPEC fully). All sibling packages now exist — full-tree builds are allowed and expected in THIS task.\n\nDeliverables:\n- internal/httpapi: loopback server per SS11 — GET /healthz; POST /internal/v1/run-exit {task_id,attempt,exit_code} validated -\u003e calls a RunExitHook (func injected by main; it pokes the reconciler); GET /api/v1/status -\u003e JSON {tasks:[TaskRecord+latest Run], generated_at}. stdlib net/http + 1.22 mux patterns; graceful shutdown; tests via httptest.\n- cmd/agenthubd: flags --config (default ./agenthub.yaml) --log-level; load config, open store, construct vikunja client, runner, no-op notifier (slog), reconciler; start httpapi + reconciler.Run; SIGINT/SIGTERM -\u003e graceful stop (context cancel, http shutdown, store close). slog JSON to stderr.\n- cmd/ahub: subcommands (stdlib flag, no cobra): `status` (GET /api/v1/status from --addr default 127.0.0.1:9100, human-readable table + --json raw), `validate-config` (load config, print OK or the collected errors, exit code accordingly), `version` (var set via -ldflags, default \"dev\").\n- justfile: verify `just build` produces bin/agenthubd bin/ahub bin/ahub-run (adjust if needed).\n- Smoke check you must run and make pass: `go build ./... \u0026\u0026 go vet ./... \u0026\u0026 go test ./...` (whole tree), then `bin/agenthubd --config config.example.yaml` with a fake VIKUNJA_TOKEN env — it must start, log the bucket-resolution failure gracefully (retry next tick, not crash-loop-exit), and /healthz must answer. Note in the bead comment if SPEC/behavior forced any deviation.\n\nConstraints: you own cmd/agenthubd, cmd/ahub, internal/httpapi, plus minimal glue edits elsewhere ONLY if a sibling package has an integration bug you must fix to link (document any such fix in its own commit '\u003cpkg\u003e: fix ...'). No .beads/ changes. Commits: 'httpapi: ...', 'cmd: ...'. Do NOT push.\n","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:35:55Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:35:55Z"}{"notes":"CONVENTION UPDATE (see SPEC §13, commit 5ce35c5+): tests use testify (require/assert); errors via auxilia culpa; agenthubd wires scribe handlers (TintHandler for ahub CLI, JSON or Multi for the daemon); steward MAY be used for daemon lifecycle wiring if it stays simple. testify + auxilia already in go.mod."}NULL2026-07-13T03:09:34Z
019f58ce-c97a-793b-a304-ad1cd3633ec5ah-nyl.7updatedEugene Blikh{"id":"ah-nyl.7","title":"e2e: harness with fake vikunja + stub pi","description":"Build the end-to-end harness per docs/SPEC.md SS1/SS14 stage-1 acceptance: prove the full loop without live services. Build tag e2e, directory e2e/, run via `just e2e`.\n\nScenario (single test, subtests per phase): temp dir with (a) a real git repo as the target repo (one commit on master), (b) fake Vikunja: httptest server implementing the subset internal/vikunja uses (views, kanban tasks, buckets, move, comment) over in-memory state you can mutate from the test; (c) stub `pi` script on PATH that reads .task/PROMPT.md, makes a commit in the cwd repo ('stub: change'), emits 2-3 plausible JSONL lines to stdout, exits 0; (d) real zellij is NOT used: stub `zellij` script that for `run` executes the wrapped command directly (background), for attach --create-background no-ops, for list-sessions prints active names from a state file — i.e. simulate sessions with files. (e) real ahub-run and agenthubd binaries built by the test (go build into t.TempDir()).\n\nFlow: start agenthubd with a generated config (short poll_interval ~200ms) -\u003e put a task in fake-Vikunja Ready bucket -\u003e wait (poll with deadline, no fixed sleeps) for: card moved to In Progress with claim comment; then card in In Review with a comment containing the diff stat; store db has task in_review + run finished/success (inspect via ahub status --json against the daemon). Negative subtest: stub pi exits 1 -\u003e card lands in Failed with diagnostic comment. Timeout subtest optional (only if cheap with the fake clock — the daemon uses real time; skip if it needs sleeps \u003e2s and note why).\n\nConstraints: everything under e2e/ (+ justfile tweak if needed). Full-tree build allowed. If you find integration bugs in other packages, fix them in separate commits '\u003cpkg\u003e: fix ...' with a test where feasible. No .beads/ changes. Commit 'e2e: ...'. Do NOT push.\n","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:35:56Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:35:56Z"}{"notes":"CONVENTION UPDATE (see SPEC §13): tests use testify (require/assert); errors via culpa. Deps already in go.mod."}NULL2026-07-13T03:09:34Z
019f58ce-cc5c-79fa-94c9-f21c46b9ed75ah-nyl.9createdEugene BlikhNULL2026-07-13T03:09:35Z
019f58ce-ce20-7a77-a90f-da8a776e7330ah-nyl.9status_changedEugene Blikh{"id":"ah-nyl.9","title":"refit: testify + culpa in domain/config/store/vikunja","description":"The repo conventions changed after these four packages landed (SPEC SS13 now): tests must use testify (github.com/stretchr/testify require/assert), and errors must be constructed/wrapped via go.bigb.es/auxilia/culpa (New/Errorf/Wrap/Wrapf; keep stdlib errors.New sentinels where callers use errors.Is). Both deps are already in go.mod.\n\nScope — exactly these packages, which are DONE and committed: internal/domain, internal/config, internal/store, internal/vikunja. Do NOT touch internal/runner, internal/reconcile, cmd/ (siblings are working there right now), go.mod/go.sum, docs/, .beads/.\n\nWork:\n1. Convert all *_test.go in the four packages to testify: require.* for fatal paths (setup, errors), assert.* for value checks where the test can meaningfully continue. Preserve every existing test case and its semantics — this is a mechanical style conversion, not a rewrite; keep table-test structures intact.\n2. Convert error construction in non-test code of the four packages from fmt.Errorf to culpa equivalents (fmt.Errorf(\"...: %w\", err) -\u003e culpa.Wrapf/Wrap; fmt.Errorf without %w -\u003e culpa.Errorf/New). Keep exported sentinels (ErrDuplicateRun, ErrRunNotFound) as-is so errors.Is keeps working; culpa-wrapped returns must still satisfy errors.Is against those sentinels where they did before (culpa supports errors.Is chains — verify with the existing duplicate-run test). Multi-error collection in config validation may stay errors.Join or move to culpa.Join — pick what keeps the error text readable and the tests passing with minimal churn.\n3. No signature changes, no behavior changes, no coverage loss. Per-package verify: go build/vet/test for ./internal/domain/... ./internal/config/... ./internal/store/... ./internal/vikunja/... (these four only; NEVER ./...).\n\nCommit per package or as one commit: 'refit: testify + culpa in \u003cpkgs\u003e' staging only the four package dirs. Hooks bypassed (git -c core.hooksPath=/tmp/nohooks commit). Do NOT push.\n","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T00:09:36Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:09:36Z"}{"status":"in_progress"}NULL2026-07-13T03:09:36Z
019f58cf-605f-708c-829a-468110db6471ah-nyl.2closedEugene Blikhlanded e3749f1, greenNULL2026-07-13T03:10:13Z
019f58cf-6268-7a73-a122-07e4b4f7bb2eah-nyl.3closedEugene Blikhlanded a1d6038, green; OpenAPI deviation from SPEC noted in package docs (flat paginated view tasks)NULL2026-07-13T03:10:13Z
019f58d3-c470-7207-bbce-66b172244fa5ah-nyl.5closedEugene Blikhlanded 2acf7c4, 11/11 scenarios + extras, race-clean, testify+culpa applied; ambiguity rulings recorded in agent report (vanish idempotency, adopt-or-fail refinement, crash-window adopt)NULL2026-07-13T03:15:01Z
019f58d8-debe-714c-9df7-dafb582290f8ah-nyl.4closedEugene Blikhlanded 4348d2b, 37 tests race-clean, empirical pi JSONL fixtures + zellij probes; SPEC corrections: git-common-dir exclude path, attach --create-background not idempotent (exit 1 tolerated), retry clears stale exit.jsonNULL2026-07-13T03:20:35Z
019f58dc-6a03-743a-af91-b710e824e9c9ah-nylupdatedEugene Blikh{"id":"ah-nyl","title":"Stage 1 MVP: poll-driven happy path","description":"Vikunja task dragged to Ready -\u003e claim -\u003e worktree+branch -\u003e pi in zellij -\u003e In Review with diff summary (or Failed with diagnostics). Everything per docs/SPEC.md (authoritative). Done when: e2e harness green + a real task passes the loop against live Vikunja.","status":"open","priority":1,"issue_type":"epic","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:34:03Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:34:03Z"}{"notes":"WORKFLOW (user directive): every implementer wave is followed by (a) hostile read-only reviewer agents and (b) empirical validator agents (real zellij/git/binaries, not stubs) BEFORE the next wave dispatches. Findings are triaged into fix beads."}NULL2026-07-13T03:24:27Z
019f58de-5657-79b8-90e8-124a9429ef4fah-nyl.9closedEugene Blikhlanded b86928a: testify+culpa across domain/config/store/vikunja, 30 top-level + 37 subtests preserved, errors.Is/As behavior verifiedNULL2026-07-13T03:26:33Z
019f58e7-7a45-7e67-9d93-ec7a3f5d777dah-nyl.10createdEugene BlikhNULL2026-07-13T03:36:32Z
019f58e7-7ca4-7f26-baa3-845f8f8e4709ah-nyl.10status_changedEugene Blikh{"id":"ah-nyl.10","title":"fix: reconcile review findings 1-6 (FIX-FIRST verdict)","description":"Hostile review of internal/reconcile @ 2acf7c4 returned FIX-FIRST with findings below. Fix ALL six (1-3 blocking, 4-5 behavior, 6 test infrastructure that would have caught 1). Line numbers refer to internal/reconcile/reconcile.go @ 2acf7c4.\n\nF1 MAJOR (:294-304,:311-330): a FINISHED latest-run row is fed to check(); runner.Status ignores attempt, so a live re-attempt gets killed on the old row's ancient StartedAt (timeout) and/or the new attempt's result is written onto the previous attempt's row. FIX: check() only when run.State == RunStateRunning; when rec.State==in_progress and latest run is finished, route to adoptOrFail and there create the missing run row as attempt = latestRun.Attempt+1 when adopting a live runtime (instead of skipping CreateRun).\n\nF2 MAJOR (:250-262): UpsertTask/CreateRun failure AFTER successful runner.Start leaves the claim retryable -\u003e next tick re-claims same attempt: Start wipes the live attempt's exit.json/events and opens a SECOND pi pane on the same worktree (duplicate paid agents). Same divergence from a daemon crash between Start and persist. FIX: (a) on persist failure after Start, compensate with best-effort runner.Kill before returning (log both errors); (b) before Start in claim, probe runner.Status for the computed attempt — if there is evidence of a live/completed runtime for it, adopt instead of double-starting.\n\nF3 MAJOR (:411-412,:216-220): comment is posted BEFORE MoveToBucket in the adoptOrFail fail-branch and the claim render-failure path -\u003e persistent move failure = a new comment every 20s forever; the claim_failed event is also appended even when the move failed. FIX: reorder both sites to move-then-comment (finalize already does this); gate the claim_failed event on move success (mirror adopt_failed).\n\nF4 MINOR (:334-347): finalize unconditionally rewrites an already-finished run row on retry (e.g. timeout kill recorded, then UpsertTask failed; next tick Status says crashed -\u003e outcome falsified timeout-\u003ecrashed, FinishedAt smeared). FIX: when run.State == RunStateFinished, skip UpdateRun and derive the target task state from the stored run.Outcome (success-\u003ein_review, else failed).\n\nF5 MINOR (:135-136): an in_progress record whose card sits in an UNMAPPED bucket is skipped entirely -\u003e no check(), no timeout; the run burns tokens unobserved. DECISION (fix this way): run check() for any record with State==in_progress regardless of the card's bucket (including unmapped and ready-awaiting-heal); card moves still only happen per the §10 bucket rules — i.e. decouple runtime supervision from card position.\n\nF6 MINOR (test honesty, fakes_test.go:290-299): (a) fakeBoard.MoveToBucket must update the task's bucket so consecutive iterations see the moved card; (b) add double-iterate zero-delta assertions (run iterate twice with unchanged inputs after claim, finalize, adopt-fail: second pass produces ZERO new side effects) — this is the level-triggered core property; (c) key fakeRunner status by (taskID, attempt) so per-attempt misattribution (F1) is expressible; add regression tests for F1 (finished run + live re-attempt: no kill, no overwrite; adoption creates attempt+1 row), F2 (persist-failure -\u003e Kill called; probe-adopt on retry), F3 (move fails -\u003e no comment posted, no claim_failed event; comment appears after move succeeds), F4 (finalize retry preserves outcome), F5 (unmapped bucket + in_progress -\u003e check still enforces timeout).\n\nConstraints: scope = internal/reconcile/ ONLY (the reviewer's suggested fixes deliberately keep runner untouched). SPEC §10 stays authoritative — where these fixes refine it (F1 routing, F3 ordering, F5 supervision decoupling), implement per THIS bead; the orchestrator will sync SPEC afterwards. Conventions: testify + culpa (SPEC §13). Build/test ONLY ./internal/reconcile/... (never ./...). Verify with -race. Commit 'reconcile: fix review findings (check routing, claim compensation, move-then-comment, finalize guard, unmapped supervision)' staging only internal/reconcile; hooks bypassed (git -c core.hooksPath=/tmp/nohooks commit); retry on index.lock; do NOT push; do NOT touch .beads/.\n","status":"open","priority":0,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-13T00:36:33Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:36:33Z"}{"status":"in_progress"}NULL2026-07-13T03:36:33Z
019f58e8-94da-7148-9e0b-903f4fa6ca9aah-nyl.11createdEugene BlikhNULL2026-07-13T03:37:45Z
019f58e8-96a0-7bc7-bf23-9b0427282819ah-nyl.11status_changedEugene Blikh{"id":"ah-nyl.11","title":"fix: runner/ahub-run review findings 1-10 (FIX-FIRST verdict)","description":"Hostile review of internal/runner + cmd/ahub-run @ HEAD (4348d2b) returned FIX-FIRST: findings 1-3 blocking, 4 strongly recommended, 5-9 hardening, 10 test gaps. Fix ALL. Line refs @ 4348d2b.\n\nF1 MAJOR (runner.go:201-225, commands.go:45-47): dir missing + branch exists + worktree still REGISTERED (human rm -rf'd it) -\u003e `git worktree add` exits 128 \"missing but already registered\"; Start wedges forever. FIX: in ensureWorktree, when the worktree dir is missing, run `git -C \u003crepo\u003e worktree prune` before add (reproduced working on git 2.55). Add a regression test that registers a worktree, rm -rf's the dir WITHOUT prune, and asserts Start succeeds.\n\nF2 MAJOR (cmd/ahub-run/main.go): no signal handling — SIGTERM/SIGINT/SIGHUP to ahub-run kills the supervisor with NO exit.json; the child pi survives until its next stdout write (SIGPIPE), burning tokens; the zellij session survives so Status says Running for the full 30m timeout, then reports the wrong outcome. FIX: signal.Notify(SIGINT, SIGTERM, SIGHUP); forward the signal to the child (process group where sensible), wait for it, then STILL write exit.json (128+sig) and POST the report. Regression test: signal a running ahub-run, assert child死 + exit.json written with 128+sig.\n\nF3 MAJOR (runner.go:103-133, 158-163): Status ignores its attempt arg (`_ = attempt`) and never checks ef.Attempt -\u003e a stale attempt-K supervisor's exit.json completes attempt K+1 with K's exit code; the truncated-then-repopulated events.jsonl attributes K's session id and costs to K+1. Kill swallows every kill-session failure at Debug, so a wedged zellij is indistinguishable from dead. FIX: (a) in Status, ef.Attempt != attempt =\u003e treat as no-exit.json and fall through to session check; (b) in Kill, after kill-session verify via list-sessions that the session is gone; if still alive, return an error (Warn+error). Regression tests for both.\n\nF4 MAJOR (runner.go:62-97): Start is not idempotent per attempt — after a claim-persist failure the reconciler re-claims the SAME attempt and Start re-prepares (truncating the live events.jsonl) and spawns a duplicate pane. FIX: at the top of Start, if .task/meta.json matches (task_id, attempt) AND exit.json is absent AND the zellij session is alive -\u003e return the existing RunInfo (worktree/branch/session) without re-preparing or re-spawning. Regression test.\n\nF5 MINOR (runner.go:344-346): sessionAlive maps non-zero exit + EMPTY output to \"no sessions\" -\u003e silent zellij failure finalizes a live run as crashed. FIX: only the recognized \"no active session\" text (or clean exit) means no-sessions; empty output + non-zero exit =\u003e return an observation error. Update the test at runner_test.go:396-403 that pins the old hazardous mapping.\n\nF6 MINOR (runner.go:406-413): runCmd merges stderr into the parsed stdout buffer -\u003e git warnings corrupt gitResolveCommonDir (exclude entry lands in a bogus silently-created path) and Summary turns stderr into fake commit lines. FIX: separate stdout/stderr buffers; parse stdout only; include stderr in error messages.\n\nF7 MINOR (taskfiles.go:61-79): WriteExitFile renames without fsync -\u003e post-power-loss empty-but-present exit.json makes ReadExitFile error forever. FIX: tmp.Sync() before Close/rename.\n\nF8 MINOR (internal/config/config.go:201-208 + commands.go:32-34): repo slug is not shape-validated; \"/\" or \"..\" in a slug makes worktreePath escape work_root. FIX in internal/config (you MAY touch config for exactly this): reject slugs not matching ^[A-Za-z0-9._-]+$ (and not \".\" / \"..\"), with a validation-collected error + test. This is the ONLY change allowed outside runner/ahub-run.\n\nF9 MINOR (events.go:49-51, 94-108): (a) a single line \u003e10MB aborts the whole scan silently (message_end lines grow with conversation) -\u003e switch to a reader that SKIPS an over-long line and continues; (b) cost summation semantics are under-pinned — fixtures have one assistant message; if pi's usage.cost.total is cumulative, += double-counts. Without re-running pi: add a two-assistant-message fixture built from the existing captured shape, document the per-message assumption in a comment referencing the fixture provenance, and add a TODO-marked guard test so the fixture is easy to re-pin from a real capture later.\n\nF10 test honesty: add the tests named above; also remove/rework TestKillDeadSessionIsNotAnError so it distinguishes \"session already gone (ok)\" from \"kill failed but session alive (error)\".\n\nConstraints: scope = internal/runner/, cmd/ahub-run/, plus the single F8 validation in internal/config/. Conventions: testify + culpa (SPEC §13). Build/vet/test ONLY your packages (./internal/runner/... ./cmd/ahub-run/... ./internal/config/...) with -race; never ./... . Another fixer is working in internal/reconcile concurrently — do not touch it. Commit 'runner: fix review findings (worktree prune, signals, per-attempt status, idempotent start, hardening)' (+ separate 'config: validate repo slug shape' commit) staging only your paths; hooks bypassed (git -c core.hooksPath=/tmp/nohooks commit); retry on index.lock; do NOT push; do NOT touch .beads/.\n","status":"open","priority":0,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-13T00:37:45Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:37:45Z"}{"status":"in_progress"}NULL2026-07-13T03:37:45Z
019f58eb-e3fb-7000-bdb5-015c39c069b6ah-nyl.12createdEugene BlikhNULL2026-07-13T03:41:22Z
019f58eb-e5af-7124-a21b-6c4d9c1d3d20ah-nyl.6updatedEugene Blikh{"id":"ah-nyl.6","title":"wiring: httpapi + agenthubd + ahub CLIs","description":"Wire the daemon together per docs/SPEC.md SS3, SS11 (read SPEC fully). All sibling packages now exist — full-tree builds are allowed and expected in THIS task.\n\nDeliverables:\n- internal/httpapi: loopback server per SS11 — GET /healthz; POST /internal/v1/run-exit {task_id,attempt,exit_code} validated -\u003e calls a RunExitHook (func injected by main; it pokes the reconciler); GET /api/v1/status -\u003e JSON {tasks:[TaskRecord+latest Run], generated_at}. stdlib net/http + 1.22 mux patterns; graceful shutdown; tests via httptest.\n- cmd/agenthubd: flags --config (default ./agenthub.yaml) --log-level; load config, open store, construct vikunja client, runner, no-op notifier (slog), reconciler; start httpapi + reconciler.Run; SIGINT/SIGTERM -\u003e graceful stop (context cancel, http shutdown, store close). slog JSON to stderr.\n- cmd/ahub: subcommands (stdlib flag, no cobra): `status` (GET /api/v1/status from --addr default 127.0.0.1:9100, human-readable table + --json raw), `validate-config` (load config, print OK or the collected errors, exit code accordingly), `version` (var set via -ldflags, default \"dev\").\n- justfile: verify `just build` produces bin/agenthubd bin/ahub bin/ahub-run (adjust if needed).\n- Smoke check you must run and make pass: `go build ./... \u0026\u0026 go vet ./... \u0026\u0026 go test ./...` (whole tree), then `bin/agenthubd --config config.example.yaml` with a fake VIKUNJA_TOKEN env — it must start, log the bucket-resolution failure gracefully (retry next tick, not crash-loop-exit), and /healthz must answer. Note in the bead comment if SPEC/behavior forced any deviation.\n\nConstraints: you own cmd/agenthubd, cmd/ahub, internal/httpapi, plus minimal glue edits elsewhere ONLY if a sibling package has an integration bug you must fix to link (document any such fix in its own commit '\u003cpkg\u003e: fix ...'). No .beads/ changes. Commits: 'httpapi: ...', 'cmd: ...'. Do NOT push.\n","notes":"CONVENTION UPDATE (see SPEC §13, commit 5ce35c5+): tests use testify (require/assert); errors via auxilia culpa; agenthubd wires scribe handlers (TintHandler for ahub CLI, JSON or Multi for the daemon); steward MAY be used for daemon lifecycle wiring if it stays simple. testify + auxilia already in go.mod.","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:35:55Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:09:34Z"}{"notes":"CONVENTION UPDATE (see SPEC §13, commit 5ce35c5+): tests use testify (require/assert); errors via auxilia culpa; agenthubd wires scribe handlers (TintHandler for ahub CLI, JSON or Multi for the daemon); steward MAY be used for daemon lifecycle wiring if it stays simple. testify + auxilia already in go.mod.\nREVIEW INPUT (data-layer review finding 4): agenthubd startup must PREFLIGHT the board — construct the vikunja client and resolve the configured bucket titles once at startup; a missing title / no-kanban-view error at that point is FATAL with a clear message (SPEC §8 'fatal config error'), while the same error later at runtime stays retryable inside the loop. Wire this into cmd/agenthubd."}NULL2026-07-13T03:41:22Z
019f58ec-8784-78f4-b1c8-44728fc87ed9ah-nyl.12updatedEugene Blikh{"id":"ah-nyl.12","title":"hardening: data-layer review fast-follows (vikunja pagination, cache refresh, config validation)","description":"Data-layer review @ b86928a returned SHIP with fast-follow hardenings. Implement all. Line refs @ b86928a.\n\nH1 (from MAJOR-latent finding, internal/vikunja/board.go:28-73): Snapshot must NEVER silently return a partial board (reconcile treats missing tasks as vanished -\u003e kills runs, cancels tasks). Fixes: (a) hitting maxSnapshotPages -\u003e return an error, not partial data; (b) when the x-pagination-total-pages header is absent, do NOT treat a short page (\u003c50) alone as end-of-data — continue until an EMPTY page (and stop on empty page in all cases); (c) if x-pagination-result-count (or total-pages) is present, cross-check the accumulated count and error on mismatch. Tests: header-absent short-page continuation, cap-hit error, mismatch error.\n\nH2 (board.go:28-35, 82-88): cache self-refresh on the read path — on a 404 from Snapshot (stale view id) or MoveToBucket's POST (stale bucket id after delete+recreate), refresh views/buckets once and retry once; second failure returns the error. Tests for both.\n\nH3 (internal/config/config.go:179-247): validate db and work_root non-empty (validation-collected errors + tests). NOTE: another fixer may have recently touched internal/config (slug validation, commit message 'config: validate repo slug shape') — pull the latest state of the file and integrate cleanly.\n\nH4 (config.go:114-124, 300-334): env-expanded plain scalars must stay strings — after substituting ${VAR} in a plain (unquoted) scalar, force the node's tag/style to !!str so a value like \"true\"/\"123\"/\"null\" cannot re-type and spuriously fail KnownFields decode. Test with an unquoted ${VAR} expanding to \"true\".\n\nH5 (internal/store/store.go:371-373): add a short code comment on the timestamp columns noting RFC3339Nano TEXT does not sort lexicographically by instant (variable-width fraction) — any future ORDER BY on time columns must ORDER BY id/seq or normalize width. Comment only, no behavior change.\n\nConstraints: scope = internal/vikunja/, internal/config/, internal/store/ (comment only). Conventions: testify + culpa. Build/vet/test -race ONLY those three packages; never ./... . Commits: 'vikunja: harden snapshot pagination and cache refresh' + 'config: require db/work_root, pin env-expanded scalars to !!str'; hooks bypassed; retry on index.lock; no push; no .beads/.\n","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T00:41:22Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:41:22Z"}{"notes":"H6 (from store validator, empirically quantified): concurrent store.New on a NONEXISTENT db file -\u003e loser fails with SQLITE_BUSY ~57% (busy_timeout does not cover first-creation/WAL-conversion during connection setup; error surfaces via PingContext, wrapped at store.go:77). Steady-state (existing file) is fully clean (93,867 hammered ops, 0 errors). FIX in internal/store: bounded retry (e.g. up to ~2s with small backoff) around the open/ping/migrate sequence in store.New when the error chain matches SQLITE_BUSY, so daemon + ahub status can race first boot safely. Add a two-process fresh-file race test if cheaply arrangeable in-package (two goroutines with separate Stores on one fresh path is enough to reproduce per the validator). Also note in the package doc that busy_timeout excludes creation."}NULL2026-07-13T03:42:04Z
019f58ee-6a8b-7301-a3df-53bb9f205694ah-nyl.11updatedEugene Blikh{"id":"ah-nyl.11","title":"fix: runner/ahub-run review findings 1-10 (FIX-FIRST verdict)","description":"Hostile review of internal/runner + cmd/ahub-run @ HEAD (4348d2b) returned FIX-FIRST: findings 1-3 blocking, 4 strongly recommended, 5-9 hardening, 10 test gaps. Fix ALL. Line refs @ 4348d2b.\n\nF1 MAJOR (runner.go:201-225, commands.go:45-47): dir missing + branch exists + worktree still REGISTERED (human rm -rf'd it) -\u003e `git worktree add` exits 128 \"missing but already registered\"; Start wedges forever. FIX: in ensureWorktree, when the worktree dir is missing, run `git -C \u003crepo\u003e worktree prune` before add (reproduced working on git 2.55). Add a regression test that registers a worktree, rm -rf's the dir WITHOUT prune, and asserts Start succeeds.\n\nF2 MAJOR (cmd/ahub-run/main.go): no signal handling — SIGTERM/SIGINT/SIGHUP to ahub-run kills the supervisor with NO exit.json; the child pi survives until its next stdout write (SIGPIPE), burning tokens; the zellij session survives so Status says Running for the full 30m timeout, then reports the wrong outcome. FIX: signal.Notify(SIGINT, SIGTERM, SIGHUP); forward the signal to the child (process group where sensible), wait for it, then STILL write exit.json (128+sig) and POST the report. Regression test: signal a running ahub-run, assert child死 + exit.json written with 128+sig.\n\nF3 MAJOR (runner.go:103-133, 158-163): Status ignores its attempt arg (`_ = attempt`) and never checks ef.Attempt -\u003e a stale attempt-K supervisor's exit.json completes attempt K+1 with K's exit code; the truncated-then-repopulated events.jsonl attributes K's session id and costs to K+1. Kill swallows every kill-session failure at Debug, so a wedged zellij is indistinguishable from dead. FIX: (a) in Status, ef.Attempt != attempt =\u003e treat as no-exit.json and fall through to session check; (b) in Kill, after kill-session verify via list-sessions that the session is gone; if still alive, return an error (Warn+error). Regression tests for both.\n\nF4 MAJOR (runner.go:62-97): Start is not idempotent per attempt — after a claim-persist failure the reconciler re-claims the SAME attempt and Start re-prepares (truncating the live events.jsonl) and spawns a duplicate pane. FIX: at the top of Start, if .task/meta.json matches (task_id, attempt) AND exit.json is absent AND the zellij session is alive -\u003e return the existing RunInfo (worktree/branch/session) without re-preparing or re-spawning. Regression test.\n\nF5 MINOR (runner.go:344-346): sessionAlive maps non-zero exit + EMPTY output to \"no sessions\" -\u003e silent zellij failure finalizes a live run as crashed. FIX: only the recognized \"no active session\" text (or clean exit) means no-sessions; empty output + non-zero exit =\u003e return an observation error. Update the test at runner_test.go:396-403 that pins the old hazardous mapping.\n\nF6 MINOR (runner.go:406-413): runCmd merges stderr into the parsed stdout buffer -\u003e git warnings corrupt gitResolveCommonDir (exclude entry lands in a bogus silently-created path) and Summary turns stderr into fake commit lines. FIX: separate stdout/stderr buffers; parse stdout only; include stderr in error messages.\n\nF7 MINOR (taskfiles.go:61-79): WriteExitFile renames without fsync -\u003e post-power-loss empty-but-present exit.json makes ReadExitFile error forever. FIX: tmp.Sync() before Close/rename.\n\nF8 MINOR (internal/config/config.go:201-208 + commands.go:32-34): repo slug is not shape-validated; \"/\" or \"..\" in a slug makes worktreePath escape work_root. FIX in internal/config (you MAY touch config for exactly this): reject slugs not matching ^[A-Za-z0-9._-]+$ (and not \".\" / \"..\"), with a validation-collected error + test. This is the ONLY change allowed outside runner/ahub-run.\n\nF9 MINOR (events.go:49-51, 94-108): (a) a single line \u003e10MB aborts the whole scan silently (message_end lines grow with conversation) -\u003e switch to a reader that SKIPS an over-long line and continues; (b) cost summation semantics are under-pinned — fixtures have one assistant message; if pi's usage.cost.total is cumulative, += double-counts. Without re-running pi: add a two-assistant-message fixture built from the existing captured shape, document the per-message assumption in a comment referencing the fixture provenance, and add a TODO-marked guard test so the fixture is easy to re-pin from a real capture later.\n\nF10 test honesty: add the tests named above; also remove/rework TestKillDeadSessionIsNotAnError so it distinguishes \"session already gone (ok)\" from \"kill failed but session alive (error)\".\n\nConstraints: scope = internal/runner/, cmd/ahub-run/, plus the single F8 validation in internal/config/. Conventions: testify + culpa (SPEC §13). Build/vet/test ONLY your packages (./internal/runner/... ./cmd/ahub-run/... ./internal/config/...) with -race; never ./... . Another fixer is working in internal/reconcile concurrently — do not touch it. Commit 'runner: fix review findings (worktree prune, signals, per-attempt status, idempotent start, hardening)' (+ separate 'config: validate repo slug shape' commit) staging only your paths; hooks bypassed (git -c core.hooksPath=/tmp/nohooks commit); retry on index.lock; do NOT push; do NOT touch .beads/.\n","status":"in_progress","priority":0,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-13T00:37:45Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:37:46Z","started_at":"2026-07-13T00:37:46Z"}{"notes":"F11 (VALIDATOR blocker D1, empirically established on real zellij 0.44.3): sessions that lived past zellij's session-serialization tick (~65s, default config) and then die WITHOUT delete-session (pi/ahub-run crash killing the pane, zellij server death, reboot with cache) remain listed by 'list-sessions --short' as bare names indistinguishable from live sessions -\u003e Status reports Running forever; OutcomeCrashed unreachable for real crashes. FIX: switch session liveness to 'zellij list-sessions --no-formatting' (plain text, dead sessions carry the '(EXITED - attach to resurrect)' suffix) and treat EXITED as NOT alive; parse defensively (name = first whitespace-separated token; EXITED detection by substring); update stub scripts + tests incl. an EXITED-listed case. Kill() keeps kill-then-delete (validator confirmed delete-session removes serialized dead sessions, exit 0). ALSO F12 (validator observation): zellij pane env = SESSION-CREATION-TIME server env, not run-client env — so bare 'ahub-run'/'pi' argv depend on the daemon's PATH at attach --create-background time. Harden: resolve ahub-run and pi to ABSOLUTE paths via exec.LookPath at Start (error clearly if not found) and use those in the pane argv; keeps working under systemd's minimal PATH later."}NULL2026-07-13T03:44:07Z
019f58fa-0a56-76e1-a023-9ae14e1605d4ah-nyl.10closedEugene Blikhlanded e863502: check routing via run.State, adopt creates attempt+1, claim probe-adopt + compensation kill, move-then-comment, finalize finished-row guard, bucket-decoupled supervision, fakes keyed by (task,attempt) + zero-delta assertions; 34 tests -race greenNULL2026-07-13T03:56:49Z
019f5902-0bc9-7a7f-a47e-45cb6663fabcah-nyl.11closedEugene Blikhlanded 7a489b9 (runner+ahub-run) + b978e0e (config slug): prune-before-add, signal handling w/ pgid, per-attempt status, idempotent start, EXITED-aware liveness via --no-formatting, absolute pane binaries, stream split, fsync, long-line skip; 5 negative controls confirmedNULL2026-07-13T04:05:34Z
019f5902-5a44-7bcf-903c-8cbd5f2b30a6ah-nyl.12status_changedEugene Blikh{"id":"ah-nyl.12","title":"hardening: data-layer review fast-follows (vikunja pagination, cache refresh, config validation)","description":"Data-layer review @ b86928a returned SHIP with fast-follow hardenings. Implement all. Line refs @ b86928a.\n\nH1 (from MAJOR-latent finding, internal/vikunja/board.go:28-73): Snapshot must NEVER silently return a partial board (reconcile treats missing tasks as vanished -\u003e kills runs, cancels tasks). Fixes: (a) hitting maxSnapshotPages -\u003e return an error, not partial data; (b) when the x-pagination-total-pages header is absent, do NOT treat a short page (\u003c50) alone as end-of-data — continue until an EMPTY page (and stop on empty page in all cases); (c) if x-pagination-result-count (or total-pages) is present, cross-check the accumulated count and error on mismatch. Tests: header-absent short-page continuation, cap-hit error, mismatch error.\n\nH2 (board.go:28-35, 82-88): cache self-refresh on the read path — on a 404 from Snapshot (stale view id) or MoveToBucket's POST (stale bucket id after delete+recreate), refresh views/buckets once and retry once; second failure returns the error. Tests for both.\n\nH3 (internal/config/config.go:179-247): validate db and work_root non-empty (validation-collected errors + tests). NOTE: another fixer may have recently touched internal/config (slug validation, commit message 'config: validate repo slug shape') — pull the latest state of the file and integrate cleanly.\n\nH4 (config.go:114-124, 300-334): env-expanded plain scalars must stay strings — after substituting ${VAR} in a plain (unquoted) scalar, force the node's tag/style to !!str so a value like \"true\"/\"123\"/\"null\" cannot re-type and spuriously fail KnownFields decode. Test with an unquoted ${VAR} expanding to \"true\".\n\nH5 (internal/store/store.go:371-373): add a short code comment on the timestamp columns noting RFC3339Nano TEXT does not sort lexicographically by instant (variable-width fraction) — any future ORDER BY on time columns must ORDER BY id/seq or normalize width. Comment only, no behavior change.\n\nConstraints: scope = internal/vikunja/, internal/config/, internal/store/ (comment only). Conventions: testify + culpa. Build/vet/test -race ONLY those three packages; never ./... . Commits: 'vikunja: harden snapshot pagination and cache refresh' + 'config: require db/work_root, pin env-expanded scalars to !!str'; hooks bypassed; retry on index.lock; no push; no .beads/.\n","notes":"H6 (from store validator, empirically quantified): concurrent store.New on a NONEXISTENT db file -\u003e loser fails with SQLITE_BUSY ~57% (busy_timeout does not cover first-creation/WAL-conversion during connection setup; error surfaces via PingContext, wrapped at store.go:77). Steady-state (existing file) is fully clean (93,867 hammered ops, 0 errors). FIX in internal/store: bounded retry (e.g. up to ~2s with small backoff) around the open/ping/migrate sequence in store.New when the error chain matches SQLITE_BUSY, so daemon + ahub status can race first boot safely. Add a two-process fresh-file race test if cheaply arrangeable in-package (two goroutines with separate Stores on one fresh path is enough to reproduce per the validator). Also note in the package doc that busy_timeout excludes creation.","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T00:41:22Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:42:04Z"}{"status":"in_progress"}NULL2026-07-13T04:05:54Z
019f5919-6f31-7dc3-8294-578d88e2546cah-nyl.12closedEugene Blikhlanded 8f1c381/4709405/878857f: pagination never-partial guarantees, cache refresh-on-404, db/work_root validation, !!str pinning, sort-order comment, first-boot BUSY retry + migrate TOCTOU serialization (BEGIN IMMEDIATE + in-tx recheck)NULL2026-07-13T04:31:06Z
019f592c-4a40-7e25-a8a3-2e2fb8156823ah-nyl.13createdEugene BlikhNULL2026-07-13T04:51:42Z
019f592c-4e4f-765c-8a0e-1e68d2a228b1ah-nyl.14createdEugene BlikhNULL2026-07-13T04:51:43Z
019f592c-5028-7626-a05a-c9bd39512fbdah-nyl.13status_changedEugene Blikh{"id":"ah-nyl.13","title":"fix: attempt-evidence composition (phantom adopt) + meta wedge + signal escalation","description":"Fix-verification review of the fix commits found a CRITICAL composition bug plus follow-ons. Findings below; line refs @ HEAD (80b565b). Scope: internal/ports (ONE additive field), internal/runner, cmd/ahub-run, internal/reconcile.\n\nCORE RULE (fixes G1+G4): a live zellij session is evidence for attempt K ONLY when .task/meta.json's attempt == K (meta is written by Start(K)). The session task-\u003cN\u003e outlives attempts (keep-pane shell after normal exit; create-background default pane), so session-alive alone is NOT evidence.\n\nG1 CRITICAL (reconcile.go:240-252 + runner.go:151-178): re-attempt after a finished run with the session alive: probe Status(task, latest+1) ignores old exit.json (attempt mismatch) and sees the alive session -\u003e Running -\u003e phantom-adopts a run row for attempt K+1 that was never Started -\u003e \"timeout\" kill ~30min later. Also defeats the interrupted-finalize guard (reconcile.go:486: !st.Running false due to keep-pane shell) so a successful attempt can be republished as \"attempt K+1 timeout\". FIX in runner.Status: in the session-alive branch read meta.json; meta.attempt == queried attempt -\u003e Running as today; meta.attempt != queried attempt (or meta absent) -\u003e this session is NOT runtime for the queried attempt: report Completed=false, Running=false is not representable... implement as: expose the meta attempt in the status and let Running mean \"session alive AND meta matches\". Concretely: add field `MetaAttempt int` (0 = unknown/absent) to ports.RunStatus with a doc comment; populate it whenever meta.json is readable; Running=true ONLY when session alive \u0026\u0026 MetaAttempt == queried attempt; when session alive \u0026\u0026 MetaAttempt != attempt -\u003e Running=false, Completed=false (a new legitimate \"no evidence for this attempt\" state — update the ports doc comment for Status accordingly: exactly one of Running/Completed OR neither when the live session belongs to a different attempt). Reconcile: runtimeEvidence stays (Running || Completed-non-crashed) — the neither-state naturally means \"no evidence\", so claim proceeds with a real Start. Verify the crash-window adopt still works (meta matches -\u003e Running -\u003e adopt).\n\nG4 MAJOR (reconcile.go:478-484 + runner.go:151-158): DB-loss adoption probes attempt 1 while the live runtime is attempt K\u003e1 -\u003e with G1's fix alone this becomes \"no evidence\" -\u003e wrongly fails the card while pi K runs unsupervised. FIX in reconcile.adoptOrFail: when the probed attempt yields no evidence but Status reports a live session with MetaAttempt M \u003e 0 and M != probed attempt, re-probe/adopt attempt M (adopt the ACTUAL in-flight attempt: create run row at attempt M, StartedAt=now). Regression test: nil latest run + live runtime with meta{attempt:3} -\u003e adopts attempt 3, no kill, no fail.\n\nG3 MAJOR (runner.go:255-263, taskfiles.go:111-128): torn/corrupt meta.json permanently wedges Start (existingRun hard-errors every tick). FIX: write meta.json atomically (same tmp+fsync+rename helper as exit.json) AND treat unparseable meta.json as absent (warn + decline reuse) in both existingRun and the G1 Status path. Regression test: garbage meta.json -\u003e Start proceeds fresh (after prune/reuse logic), Status doesn't error.\n\nG5 MINOR (cmd/ahub-run/main.go:127-136): child ignoring SIGTERM/SIGHUP -\u003e ahub-run waits forever, session killed under it, token burn. FIX: after forwarding the signal, bounded wait (10s) then SIGKILL the child process group; still write exit.json+report. Test with a TERM-ignoring child script.\n\nG6 MINOR (reconcile.go:294-298): persistent Start failure (e.g. pi not on PATH) -\u003e error event appended EVERY tick, unbounded, card stuck in ready. FIX: dedup — skip appending when the task's most recent event has identical kind+payload (cheap: track last event per task in-memory in the Reconciler); AND after 5 consecutive start failures for the same (task, attempt), move the card to failed with a comment (move-then-comment) so the operator sees it. Tests for both.\n\nG7 MINOR (reconcile.go:486-496): dragging an already-finalized card back to in_progress re-runs finalize with moveCard=true -\u003e duplicate result comment per drag. FIX: fire the interrupted-finalize completion branch with moveCard=true only when rec.State == in_progress; for terminal rec.State just align per the §10 terminal rules. Test: drag in_review card to in_progress with dead session -\u003e no duplicate comment, converges.\n\nConstraints: scope exactly internal/ports (RunStatus field + doc), internal/runner, cmd/ahub-run, internal/reconcile. Do NOT touch internal/vikunja (a sibling fixer works there concurrently), internal/store, internal/config, docs/, .beads/, go.mod. Conventions testify+culpa. Build/vet/test -race ONLY ./internal/ports/... ./internal/runner/... ./cmd/ahub-run/... ./internal/reconcile/... ; never ./... . Update the reconcile fakes so an unscripted (task,attempt) Status returns the realistic three-state contract (the zero-value RunStatus masked G1 — make fakes fail loudly on unscripted queries instead). Commits: 'runner: attempt-evidence via meta.json (+atomic meta, signal escalation)' + 'reconcile: adopt actual in-flight attempt, start-failure backoff, refinalize guard'; hooks bypassed; retry on index.lock; no push.\n","status":"open","priority":0,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-13T01:51:43Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T01:51:43Z"}{"status":"in_progress"}NULL2026-07-13T04:51:44Z
019f592c-51c2-711d-818a-148c3f037548ah-nyl.14status_changedEugene Blikh{"id":"ah-nyl.14","title":"fix: vikunja result-count header semantics (verify against source)","description":"Fix-verification review flagged H1's cross-check as likely wrong (CRITICAL-risk): board.go:107-131 compares the ACCUMULATED task total against the last-seen x-pagination-result-count header. If Vikunja's header means \"items in the CURRENT response\" (per-page) — which is the documented/likely semantics — any board \u003e1 page makes snapshotOnce error on EVERY iteration -\u003e reconcile aborts every pass -\u003e no supervision, no timeouts, daemon effectively stalls at \u003e50 tasks. Also the empty terminal page's headers are checked BEFORE the empty-page break (board.go:104-113), which under per-page semantics errors the headerless path too.\n\nSTEP 1 — establish the truth from Vikunja 2.3 SOURCE (do not guess): fetch the pagination handler from the upstream repo (github.com/go-vikunja/vikunja, tag v2.3.0 or close; the header is set in pkg/web/handler/ — search for \"x-pagination-result-count\"). Record the exact semantics (total vs per-page) with a file/line citation in a code comment.\n\nSTEP 2 — fix accordingly. If per-page (expected): cross-check len(page tasks) vs the header PER RESPONSE (mismatch -\u003e error), keep x-pagination-total-pages as the page-count terminator, keep empty-page as universal terminator, keep cap-hit -\u003e error; move the empty-page break BEFORE any header cross-checks. If genuinely total: keep the accumulated check but STILL move the empty-page break first and add the missing decisive fixtures. Either way add: multi-page fixture WITH result-count headers on every page; empty-terminal-page-with-headers fixture; headerless multi-page fixture (already exists — keep).\n\nConstraints: scope = internal/vikunja/ ONLY (a sibling fixer works in runner/reconcile/ports concurrently). Conventions testify+culpa. Build/vet/test -race ./internal/vikunja/... only. Commit 'vikunja: fix result-count semantics per upstream source (\u003ccitation\u003e)'; hooks bypassed; retry on index.lock; no push; no .beads/.\n","status":"open","priority":0,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-13T01:51:44Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T01:51:44Z"}{"status":"in_progress"}NULL2026-07-13T04:51:44Z
019f5933-ec47-7975-9253-097c84f8b96eah-nyl.14closedEugene Blikhlanded 7802095: per-response result-count check (semantics proven from vikunja v2.3.0 pkg/web/handler/read_all.go with line citations), empty-page break before header checks, decisive multi-page fixturesNULL2026-07-13T05:00:02Z
019f594b-dc01-75b1-9082-e8dae6ca2183ah-nyl.13closedEugene Blikhlanded a0c8858+24cb927: meta-aware attempt evidence (three-state Status w/ MetaAttempt), atomic meta + lenient corrupt handling, adopt actual in-flight attempt, SIGKILL escalation, start-failure dedup+escalation, refinalize bounce-back; fakes panic on unscripted queries; six traces pinnedNULL2026-07-13T05:26:11Z
019f595b-cb67-7dd3-acb8-3ff323d23f19ah-nyl.15createdEugene BlikhNULL2026-07-13T05:43:35Z