main · last commit
13 days ago ·
7g0stsfu
ah-1cx.11 [bug] createChildTasks swallows RecordChild failures, orphaning a child from the join
Past Stand
bd reopen ah-1cx.11
| Created by | Eugene Blikh |
| Owner | bigbes@gmail.com |
| Created | 2026-08-04T23:43:10Z |
| Started | 2026-08-04T23:54:11Z |
| Updated | 2026-08-05T00:23:05Z |
| Closed | 2026-08-05T00:23:05Z |
Found while fixing ah-1cx.2. createChildTasks logs and continues when RecordChild fails (internal/reconcile/tools.go:376). A child created on the board whose lineage row is lost is invisible to both ChildIDsOf and the parent's join: the parent parks in Blocked waiting on a set that does not include it, so the child can complete without ever unblocking the parent — and a human sees a blocked card with no visible reason. It also weakens the before-count guard ah-1cx.2 added, which distinguishes a real pending join from fire-and-forget children by comparing the parent's child count before and after delegation.
A RecordChild failure either aborts the delegation loudly (card held, comment, audit event) or is retried durably. Either way the parent never parks on an incomplete child set. Covered by a test.
ah-1cx
— Hardening: post-audit bug sweep (2026-07-20)
parent-child
open
Nothing depends on this issue.
| id | ah-1cx.11 |
| content_hash | 39a21e11c8e682fefa03a720759e6bc1b9c78a3b57baf86938f388843b7556e3 |
| title | [bug] createChildTasks swallows RecordChild failures, orphaning a child from the join |
| description | Found while fixing ah-1cx.2. createChildTasks logs and continues when RecordChild fails (internal/reconcile/tools.go:376). A child created on the board whose lineage row is lost is invisible to both ChildIDsOf and the parent's join: the parent parks in Blocked waiting on a set that does not include it, so the child can complete without ever unblocking the parent — and a human sees a blocked card with no visible reason. It also weakens the before-count guard ah-1cx.2 added, which distinguishes a real pending join from fire-and-forget children by comparing the parent's child count before and after delegation. |
| design | |
| acceptance_criteria | A RecordChild failure either aborts the delegation loudly (card held, comment, audit event) or is retried durably. Either way the parent never parks on an incomplete child set. Covered by a test. |
| notes | |
| status | closed |
| priority | 3 |
| issue_type | bug |
| assignee | NULL |
| estimated_minutes | NULL |
| created_at | 2026-08-04T23:43:10Z |
| created_by | Eugene Blikh |
| owner | bigbes@gmail.com |
| updated_at | 2026-08-05T00:23:05Z |
| closed_at | 2026-08-05T00:23:05Z |
| 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 | Done in 8716a2a. Confirmed as filed: createChildTasks logged and continued past a RecordChild failure, so a child could exist on the board with no task_parents row — invisible to ChildIDsOf, to the Blocked join, and to the per-parent lifetime cap — leaving the parent parked on a strictly smaller set than it created and undercounting ah-1cx.2's before/after guard. Took the DURABLE-RETRY option rather than a plain abort, because an abort that holds the card would hold it forever. createChildTasks now stops at the FIRST lost lineage row (creating more children a broken store cannot record only widens the hole) and returns the orphaned ids alongside created. delegateAndBlock refuses to park while any id is orphaned: it records them in the park marker's payload (armed:<before>,<id>,<id> — the no-orphan form is unchanged so older markers decode), holds the card in In Progress, and posts a comment naming every created card plus the untracked ones, stamped alarmed so a persistent fault does not spam. reparkDelegatedParent retries the lineage write before anything counts children (RecordChild is idempotent; the generation is re-derived from GenerationOf(parent)+1 rather than stored), then parks normally and records a delegation_lineage_recovered event. ToolTaskCreate reports the same condition as a 500 naming the card id — a fire-and-forget child deadlocks nobody, but a silent success would still corrupt the cap and any later join. Cover: TestDelegationLineageFailure, TestToolTaskCreateLineageFailureIsReported. FOLLOW-UP: nothing reconciles board reality against task_parents, so a child card created by hand or a lineage row whose card was deleted is still undetectable — a periodic lineage audit would close that. |
| 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:54:11Z |
| is_blocked | 0 |
| issue_id | ah-1cx.11 |
| label | milestone:v0.2.0 |
| id | 2bb7505d-7292-5fd9-999b-5e8616f32fd8 |
| issue_id | ah-1cx.11 |
| type | parent-child |
| created_at | 2026-08-05T02:43:10Z |
| created_by | Eugene Blikh |
| metadata | �{} |
| thread_id | |
| depends_on_issue_id | ah-1cx |
| depends_on_wisp_id | NULL |
| depends_on_external | NULL |
| id | 019fcf28-df25-7a9e-b4b7-d7ca76c37a41 |
| issue_id | ah-1cx.11 |
| event_type | created |
| actor | Eugene Blikh |
| old_value | |
| new_value | |
| comment | NULL |
| created_at | 2026-08-05T02:43:10Z |
| id | 019fcf32-f633-76ab-94a3-e781f2fb1972 |
| issue_id | ah-1cx.11 |
| event_type | status_changed |
| actor | Eugene Blikh |
| old_value | {"id":"ah-1cx.11","title":"[bug] createChildTasks swallows RecordChild failures, orphaning a child from the join","description":"Found while fixing ah-1cx.2.\n\ncreateChildTasks logs and continues when RecordChild fails (internal/reconcile/tools.go:376). A child created on the board whose lineage row is lost is invisible to both ChildIDsOf and the parent's join: the parent parks in Blocked waiting on a set that does not include it, so the child can complete without ever unblocking the parent — and a human sees a blocked card with no visible reason.\n\nIt also weakens the before-count guard ah-1cx.2 added, which distinguishes a real pending join from fire-and-forget children by comparing the parent's child count before and after delegation.","acceptance_criteria":"A RecordChild failure either aborts the delegation loudly (card held, comment, audit event) or is retried durably. Either way the parent never parks on an incomplete child set. Covered by a test.","status":"open","priority":3,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-08-04T23:43:10Z","created_by":"Eugene Blikh","updated_at":"2026-08-04T23:43:10Z"} |
| new_value | {"status":"in_progress"} |
| comment | NULL |
| created_at | 2026-08-05T02:54:11Z |
| id | 019fcf42-1930-73ad-8d9b-b7f5ce1cc570 |
| issue_id | ah-1cx.11 |
| event_type | label_added |
| actor | Eugene Blikh |
| old_value | NULL |
| new_value | NULL |
| comment | Added label: milestone:v0.2.0 |
| created_at | 2026-08-05T03:10:43Z |
| id | 019fcf4d-6b0c-7226-84bf-6954423c12ef |
| issue_id | ah-1cx.11 |
| event_type | closed |
| actor | Eugene Blikh |
| old_value | |
| new_value | Done in 8716a2a. Confirmed as filed: createChildTasks logged and continued past a RecordChild failure, so a child could exist on the board with no task_parents row — invisible to ChildIDsOf, to the Blocked join, and to the per-parent lifetime cap — leaving the parent parked on a strictly smaller set than it created and undercounting ah-1cx.2's before/after guard. Took the DURABLE-RETRY option rather than a plain abort, because an abort that holds the card would hold it forever. createChildTasks now stops at the FIRST lost lineage row (creating more children a broken store cannot record only widens the hole) and returns the orphaned ids alongside created. delegateAndBlock refuses to park while any id is orphaned: it records them in the park marker's payload (armed:<before>,<id>,<id> — the no-orphan form is unchanged so older markers decode), holds the card in In Progress, and posts a comment naming every created card plus the untracked ones, stamped alarmed so a persistent fault does not spam. reparkDelegatedParent retries the lineage write before anything counts children (RecordChild is idempotent; the generation is re-derived from GenerationOf(parent)+1 rather than stored), then parks normally and records a delegation_lineage_recovered event. ToolTaskCreate reports the same condition as a 500 naming the card id — a fire-and-forget child deadlocks nobody, but a silent success would still corrupt the cap and any later join. Cover: TestDelegationLineageFailure, TestToolTaskCreateLineageFailureIsReported. FOLLOW-UP: nothing reconciles board reality against task_parents, so a child card created by hand or a lineage row whose card was deleted is still undetectable — a periodic lineage audit would close that. |
| comment | NULL |
| created_at | 2026-08-05T03:23:05Z |
No comments.
Close reason