main · last commit
13 days ago ·
7g0stsfu
ah-1cx.2 Delegation persist failure orphans children: parent re-finalizes to In Review, never Blocked
Past Stand
bd reopen ah-1cx.2
| Created by | Eugene Blikh |
| Owner | bigbes@gmail.com |
| Created | 2026-07-19T23:37:32Z |
| Started | 2026-08-04T23:32:43Z |
| Updated | 2026-08-04T23:41:33Z |
| Closed | 2026-08-04T23:41:33Z |
When delegateAndBlock has already created child cards and recorded lineage but its UpsertTask(Blocked) fails (reconcile.go:921-930), the stored record stays in_progress while the run row is finished. Next tick adoptOrFail takes the interrupted-finalize path and re-finalizes with firstFinalize=false, so delegation is skipped and the card lands In Review — the children run to completion but the parent never joins or resumes to integrate them. The code comment claims the heal path converges to Blocked; it does not (only the sibling MoveToBucket-failure case converges via alignCardToRecord). Fix: re-attempt the Blocked persist (retry signal) instead of letting a finished-row re-finalize strand the children. Rare trigger (store write failure at exactly that step); confirmed by reading, core-runtime audit 2026-07-20.
ah-1cx
— Hardening: post-audit bug sweep (2026-07-20)
parent-child
open
Nothing depends on this issue.
| id | ah-1cx.2 |
| content_hash | b631a20368ede9c76697a930cc68dfee0415035e0dd41ffb9b8b01666ce3ce31 |
| title | Delegation persist failure orphans children: parent re-finalizes to In Review, never Blocked |
| description | When delegateAndBlock has already created child cards and recorded lineage but its UpsertTask(Blocked) fails (reconcile.go:921-930), the stored record stays in_progress while the run row is finished. Next tick adoptOrFail takes the interrupted-finalize path and re-finalizes with firstFinalize=false, so delegation is skipped and the card lands In Review — the children run to completion but the parent never joins or resumes to integrate them. The code comment claims the heal path converges to Blocked; it does not (only the sibling MoveToBucket-failure case converges via alignCardToRecord). Fix: re-attempt the Blocked persist (retry signal) instead of letting a finished-row re-finalize strand the children. Rare trigger (store write failure at exactly that step); confirmed by reading, core-runtime audit 2026-07-20. |
| design | |
| acceptance_criteria | |
| notes | |
| status | closed |
| priority | 3 |
| issue_type | bug |
| assignee | NULL |
| estimated_minutes | NULL |
| created_at | 2026-07-19T23:37:32Z |
| created_by | Eugene Blikh |
| owner | bigbes@gmail.com |
| updated_at | 2026-08-04T23:41:33Z |
| closed_at | 2026-08-04T23:41:33Z |
| 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 | Fixed in 67dcf3c. Confirmed by REPRODUCTION, not just reading: finalize computes firstFinalize from run.State != finished and the delegation branch was gated on it; when delegateAndBlock's UpsertTask(Blocked) failed, the run row was already finished (UpdateRun runs first) while the task record stayed in_progress, so the next tick's adoptOrFail interrupted-finalize path re-entered finalize with firstFinalize=false, skipped delegation entirely, and routed the parent to In Review behind live children. The old comment claiming the heal path converges to Blocked was WRONG — alignCardToRecord only covers the sibling MoveToBucket-failure case, where the record is already Blocked. Fix: delegateAndBlock arms a durable intent marker in the existing kv store (delegation_park/<task>/<attempt>, value armed:<n>/alarmed:<n> where n is the parent's child count BEFORE this delegation) ahead of createChildTasks, so the only write that can fail before any child exists is the marker itself, and that failure cancels the delegation outright instead of half-landing it. The park tail is extracted into an idempotent parkParentBlocked used by both the first finalization and a new refinalize-only reparkDelegatedParent, which re-attempts the flip from the marker and NEVER calls createChildTasks — so a heal cannot become a duplicate-child bug. The before-count is what distinguishes a real pending join from fire-and-forget children created by the mid-run ToolTaskCreate channel and from an armed-but-created-nothing delegation; both disarm and finalize to In Review as before. Failure is now loud and recoverable: error log, delegation_park_failed audit event, and a once-per-attempt card comment (guarded by the marker's alarmed flag so a broken store cannot spam the card), with the card held in In Progress so the loop retries each tick. Run-row immutability untouched. Cover: TestDelegationParkPersistFailure (4 subtests) + TestDelegationParkGuards (4 subtests). |
| 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-08-04T23:32:43Z |
| is_blocked | 0 |
| issue_id | ah-1cx.2 |
| label | milestone:v0.1.0 |
| id | ade21fac-04c6-5276-aded-7071f2917cc2 |
| issue_id | ah-1cx.2 |
| type | parent-child |
| created_at | 2026-07-20T02:37:31Z |
| created_by | Eugene Blikh |
| metadata | �{} |
| thread_id | |
| depends_on_issue_id | ah-1cx |
| depends_on_wisp_id | NULL |
| depends_on_external | NULL |
| id | 019f7cbd-f533-72d6-8f31-87f8a7699fbb |
| issue_id | ah-1cx.2 |
| event_type | created |
| actor | Eugene Blikh |
| old_value | |
| new_value | |
| comment | NULL |
| created_at | 2026-07-20T02:37:31Z |
| id | 019f7cda-2a1c-7ad5-b06b-ba03c76c502d |
| issue_id | ah-1cx.2 |
| event_type | label_added |
| actor | Eugene Blikh |
| old_value | NULL |
| new_value | NULL |
| comment | Added label: milestone:hardening |
| created_at | 2026-07-20T03:08:20Z |
| id | 019f7cdb-ebea-70d2-8fe2-43814c575bd6 |
| issue_id | ah-1cx.2 |
| event_type | label_removed |
| actor | Eugene Blikh |
| old_value | NULL |
| new_value | NULL |
| comment | Removed label: milestone:hardening |
| created_at | 2026-07-20T03:10:15Z |
| id | 019f7cdc-fae8-7d5d-aa9d-8cc0e5893f79 |
| issue_id | ah-1cx.2 |
| event_type | label_added |
| actor | Eugene Blikh |
| old_value | NULL |
| new_value | NULL |
| comment | Added label: milestone:v0.1.0 |
| created_at | 2026-07-20T03:11:24Z |
| id | 019fcf1f-4f41-7897-a3e3-d9700e0fabb2 |
| issue_id | ah-1cx.2 |
| event_type | status_changed |
| actor | Eugene Blikh |
| old_value | {"id":"ah-1cx.2","title":"Delegation persist failure orphans children: parent re-finalizes to In Review, never Blocked","description":"When delegateAndBlock has already created child cards and recorded lineage but its UpsertTask(Blocked) fails (reconcile.go:921-930), the stored record stays in_progress while the run row is finished. Next tick adoptOrFail takes the interrupted-finalize path and re-finalizes with firstFinalize=false, so delegation is skipped and the card lands In Review — the children run to completion but the parent never joins or resumes to integrate them. The code comment claims the heal path converges to Blocked; it does not (only the sibling MoveToBucket-failure case converges via alignCardToRecord). Fix: re-attempt the Blocked persist (retry signal) instead of letting a finished-row re-finalize strand the children. Rare trigger (store write failure at exactly that step); confirmed by reading, core-runtime audit 2026-07-20.","status":"open","priority":3,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-19T23:37:32Z","created_by":"Eugene Blikh","updated_at":"2026-07-19T23:37:32Z","labels":["milestone:v0.1.0"]} |
| new_value | {"status":"in_progress"} |
| comment | NULL |
| created_at | 2026-08-05T02:32:43Z |
| id | 019fcf27-643e-782d-a052-a620b8e39e4c |
| issue_id | ah-1cx.2 |
| event_type | closed |
| actor | Eugene Blikh |
| old_value | |
| new_value | Fixed in 67dcf3c. Confirmed by REPRODUCTION, not just reading: finalize computes firstFinalize from run.State != finished and the delegation branch was gated on it; when delegateAndBlock's UpsertTask(Blocked) failed, the run row was already finished (UpdateRun runs first) while the task record stayed in_progress, so the next tick's adoptOrFail interrupted-finalize path re-entered finalize with firstFinalize=false, skipped delegation entirely, and routed the parent to In Review behind live children. The old comment claiming the heal path converges to Blocked was WRONG — alignCardToRecord only covers the sibling MoveToBucket-failure case, where the record is already Blocked. Fix: delegateAndBlock arms a durable intent marker in the existing kv store (delegation_park/<task>/<attempt>, value armed:<n>/alarmed:<n> where n is the parent's child count BEFORE this delegation) ahead of createChildTasks, so the only write that can fail before any child exists is the marker itself, and that failure cancels the delegation outright instead of half-landing it. The park tail is extracted into an idempotent parkParentBlocked used by both the first finalization and a new refinalize-only reparkDelegatedParent, which re-attempts the flip from the marker and NEVER calls createChildTasks — so a heal cannot become a duplicate-child bug. The before-count is what distinguishes a real pending join from fire-and-forget children created by the mid-run ToolTaskCreate channel and from an armed-but-created-nothing delegation; both disarm and finalize to In Review as before. Failure is now loud and recoverable: error log, delegation_park_failed audit event, and a once-per-attempt card comment (guarded by the marker's alarmed flag so a broken store cannot spam the card), with the card held in In Progress so the loop retries each tick. Run-row immutability untouched. Cover: TestDelegationParkPersistFailure (4 subtests) + TestDelegationParkGuards (4 subtests). |
| comment | NULL |
| created_at | 2026-08-05T02:41:33Z |
No comments.
Close reason