main · last commit
13 days ago ·
7g0stsfu
ah-nyl.11 fix: runner/ahub-run review findings 1-10 (FIX-FIRST verdict)
Past Stand
bd reopen ah-nyl.11
| Created by | Eugene Blikh |
| Owner | bigbes@gmail.com |
| Created | 2026-07-13T00:37:45Z |
| Started | 2026-07-13T00:37:46Z |
| Updated | 2026-07-13T01:05:34Z |
| Closed | 2026-07-13T01:05:34Z |
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. F1 MAJOR (runner.go:201-225, commands.go:45-47): dir missing + branch exists + worktree still REGISTERED (human rm -rf'd it) -> `git worktree add` exits 128 "missing but already registered"; Start wedges forever. FIX: in ensureWorktree, when the worktree dir is missing, run `git -C <repo> 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. F2 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. F3 MAJOR (runner.go:103-133, 158-163): Status ignores its attempt arg (`_ = attempt`) and never checks ef.Attempt -> 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 => 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. F4 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 -> return the existing RunInfo (worktree/branch/session) without re-preparing or re-spawning. Regression test. F5 MINOR (runner.go:344-346): sessionAlive maps non-zero exit + EMPTY output to "no sessions" -> 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 => return an observation error. Update the test at runner_test.go:396-403 that pins the old hazardous mapping. F6 MINOR (runner.go:406-413): runCmd merges stderr into the parsed stdout buffer -> 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. F7 MINOR (taskfiles.go:61-79): WriteExitFile renames without fsync -> post-power-loss empty-but-present exit.json makes ReadExitFile error forever. FIX: tmp.Sync() before Close/rename. F8 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. F9 MINOR (events.go:49-51, 94-108): (a) a single line >10MB aborts the whole scan silently (message_end lines grow with conversation) -> 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. F10 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)". Constraints: 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/.
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 -> 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.
ah-nyl
— Stage 1 MVP: poll-driven happy path
parent-child
closed
Nothing depends on this issue.
| id | ah-nyl.11 |
| content_hash | ccf963c6cdbbcf9043784dbdf1f98f71af8baca8cbd9a58cca23607dc1ff7f92 |
| 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. F1 MAJOR (runner.go:201-225, commands.go:45-47): dir missing + branch exists + worktree still REGISTERED (human rm -rf'd it) -> `git worktree add` exits 128 "missing but already registered"; Start wedges forever. FIX: in ensureWorktree, when the worktree dir is missing, run `git -C <repo> 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. F2 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. F3 MAJOR (runner.go:103-133, 158-163): Status ignores its attempt arg (`_ = attempt`) and never checks ef.Attempt -> 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 => 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. F4 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 -> return the existing RunInfo (worktree/branch/session) without re-preparing or re-spawning. Regression test. F5 MINOR (runner.go:344-346): sessionAlive maps non-zero exit + EMPTY output to "no sessions" -> 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 => return an observation error. Update the test at runner_test.go:396-403 that pins the old hazardous mapping. F6 MINOR (runner.go:406-413): runCmd merges stderr into the parsed stdout buffer -> 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. F7 MINOR (taskfiles.go:61-79): WriteExitFile renames without fsync -> post-power-loss empty-but-present exit.json makes ReadExitFile error forever. FIX: tmp.Sync() before Close/rename. F8 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. F9 MINOR (events.go:49-51, 94-108): (a) a single line >10MB aborts the whole scan silently (message_end lines grow with conversation) -> 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. F10 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)". Constraints: 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/. |
| design | |
| acceptance_criteria | |
| 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 -> 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. |
| status | closed |
| priority | 0 |
| issue_type | bug |
| assignee | NULL |
| estimated_minutes | NULL |
| created_at | 2026-07-13T00:37:45Z |
| created_by | Eugene Blikh |
| owner | bigbes@gmail.com |
| updated_at | 2026-07-13T01:05:34Z |
| closed_at | 2026-07-13T01:05:34Z |
| closed_by_session | |
| external_ref | NULL |
| spec_id | |
| compaction_level | 0 |
| compacted_at | NULL |
| compacted_at_commit | NULL |
| original_size | NULL |
| sender | |
| ephemeral | 0 |
| wisp_type | |
| pinned | 0 |
| is_template | 0 |
| mol_type | |
| work_type | |
| source_system | |
| metadata | �{} |
| source_repo | |
| close_reason | landed 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 confirmed |
| event_kind | |
| actor | |
| target | |
| payload | |
| await_type | |
| await_id | |
| timeout_ns | 0 |
| waiters | |
| hook_bead | |
| role_bead | |
| agent_state | |
| last_activity | NULL |
| role_type | |
| rig | |
| due_at | NULL |
| defer_until | NULL |
| no_history | 0 |
| started_at | 2026-07-13T00:37:46Z |
| is_blocked | 0 |
| id | 2445d556-ac08-56f8-88f6-40ad5ab239e9 |
| issue_id | ah-nyl.11 |
| type | parent-child |
| created_at | 2026-07-13T03:37:45Z |
| created_by | Eugene Blikh |
| metadata | �{} |
| thread_id | |
| depends_on_issue_id | ah-nyl |
| depends_on_wisp_id | NULL |
| depends_on_external | NULL |
| id | 019f58e8-94da-7148-9e0b-903f4fa6ca9a |
| issue_id | ah-nyl.11 |
| event_type | created |
| actor | Eugene Blikh |
| old_value | |
| new_value | |
| comment | NULL |
| created_at | 2026-07-13T03:37:45Z |
| id | 019f58e8-96a0-7bc7-bf23-9b0427282819 |
| issue_id | ah-nyl.11 |
| event_type | status_changed |
| actor | Eugene Blikh |
| old_value | {"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"} |
| new_value | {"status":"in_progress"} |
| comment | NULL |
| created_at | 2026-07-13T03:37:45Z |
| id | 019f58ee-6a8b-7301-a3df-53bb9f205694 |
| issue_id | ah-nyl.11 |
| event_type | updated |
| actor | Eugene Blikh |
| old_value | {"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"} |
| new_value | {"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."} |
| comment | NULL |
| created_at | 2026-07-13T03:44:07Z |
| id | 019f5902-0bc9-7a7f-a47e-45cb6663fabc |
| issue_id | ah-nyl.11 |
| event_type | closed |
| actor | Eugene Blikh |
| old_value | |
| new_value | landed 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 confirmed |
| comment | NULL |
| created_at | 2026-07-13T04:05:34Z |
| id | 019f7cda-9083-7cb9-a9b3-45a60b99b5ca |
| issue_id | ah-nyl.11 |
| event_type | label_added |
| actor | Eugene Blikh |
| old_value | NULL |
| new_value | NULL |
| comment | Added label: milestone:stage-1 |
| created_at | 2026-07-20T03:08:46Z |
| id | 019f7cdb-fc5c-719a-96be-a8265b0cc49a |
| issue_id | ah-nyl.11 |
| event_type | label_removed |
| actor | Eugene Blikh |
| old_value | NULL |
| new_value | NULL |
| comment | Removed label: milestone:stage-1 |
| created_at | 2026-07-20T03:10:19Z |
No comments.
Close reason