-
Notifications
You must be signed in to change notification settings - Fork 0
Pipeline Design 232
I now have a complete picture. The fix is already implemented — the code and tests are in place. Let me write the ADR.
When a pipeline resumes (or a new pipeline starts on a branch that previously ran a different issue), pipeline-tasks.md — the file injected into the build stage prompt as a task checklist — was not being cleared. This caused stale tasks from a prior issue to leak into the current build loop, confusing the build agent with irrelevant or completed work items.
The root file is .claude/pipeline-tasks.md, which contains an - Issue: #N metadata line in its ## Context section. Three code paths touch this file:
-
initialize_state()(scripts/lib/pipeline-state.sh:452) — called on fresh pipeline start -
resume_state()(scripts/lib/pipeline-state.sh:536) — called onshipwright pipeline resume -
stage_build()(scripts/lib/pipeline-stages-build.sh:172) — reads the file and injects its contents into the build prompt
A shared helper extract_issue_from_tasks_file() (scripts/lib/helpers.sh:429) normalizes the issue number from the file's metadata header for comparison.
Defense-in-depth at all three touchpoints — each layer independently prevents stale task injection:
-
initialize_state()(line 463): Unconditionally deletes$TASKS_FILEon every new pipeline start. No issue comparison needed — a fresh run always starts clean. -
resume_state()(lines 607–620): After restoring state frompipeline-state.md, validates the tasks file:- If the file exists but lacks a parseable
- Issue:header → malformed → delete it. - If the file's issue number differs from the resumed pipeline's
$GITHUB_ISSUE→ stale → delete it. - If the issue matches → keep it (preserves legitimate progress).
- If the file exists but lacks a parseable
-
stage_build()(lines 172–188): As a final guard at injection time, repeats the same validation before appending tasks to the build prompt. Stale or malformed files are removed and a warning is logged, but the build continues without task context.
Validation logic uses extract_issue_from_tasks_file() which:
- Returns exit code 1 if the file is missing, unreadable, or lacks an
- Issue:line - Strips
#prefix and whitespace for normalized comparison - Handles both
- Issue:andIssue:formats, case-insensitive
Edge cases handled:
-
TASKS_FILEvariable unset or empty →[[ -n "${TASKS_FILE:-}" ]]guards ininitialize_state;[[ -s "${TASKS_FILE:-}" ]]guards inresume_stateandstage_build -
GITHUB_ISSUEunset (goal-only pipeline, no issue) →current_issuewill be empty; comparison"$tasks_issue" != "$current_issue"evaluates true only whencurrent_issueis non-empty, so tasks are kept when there's no issue to compare against - File present but empty (0 bytes) →
-stest fails, file is ignored
-
Clear only in
initialize_state()— Pros: single change point, simplest diff / Cons: doesn't protect resume path; a user could manually create a tasks file between runs that wouldn't be validated. Insufficient for the resume scenario which is the primary bug. -
Add issue metadata to pipeline-state.md and skip tasks file entirely — Pros: eliminates the stale-file class of bugs entirely / Cons: major refactor of the build stage prompt assembly; tasks file is also used by the loop system (
sw-loop-test.sh:1185) and bookkeeping exclusion lists (helpers.sh:394). Too much blast radius for a bug fix. -
Timestamp-based staleness (clear if tasks file is older than pipeline start) — Pros: no issue parsing needed / Cons: fragile on resume (legitimate tasks from a paused run would be incorrectly cleared); doesn't handle the case where two different issues run on the same branch in quick succession.
- Files to create: none
-
Files to modify:
-
scripts/lib/pipeline-state.sh—initialize_state()(line 463): addrm -f "$TASKS_FILE";resume_state()(lines 607–620): add issue-match validation block -
scripts/lib/pipeline-stages-build.sh—stage_build()(lines 172–188): add issue-match guard before injection -
scripts/lib/helpers.sh—extract_issue_from_tasks_file()(lines 429–435): new shared extraction helper -
scripts/sw-lib-pipeline-stages-test.sh— (lines 385–496+): tests for all three defense layers
-
-
Dependencies: none (uses existing
grep,sed,xargs) -
Risk areas:
-
extract_issue_from_tasks_filegrep pattern must match both- Issue: #42andIssue: 42formats — covered by the regex^-\{0,1\} *Issue: - Goal-only pipelines (no
GITHUB_ISSUE) should not falsely clear a valid tasks file — handled by the[[ -n "$current_issue" ]]guard
-
-
initialize_state()deletes$TASKS_FILEunconditionally on new pipeline start -
resume_state()deletes tasks file when issue number differs from resumed pipeline -
resume_state()preserves tasks file when issue number matches -
resume_state()deletes malformed tasks file (no- Issue:header) -
stage_build()refuses to inject tasks from a mismatched issue and removes the file -
extract_issue_from_tasks_file()handles#-prefixed and bare issue numbers - All existing tests pass (
npm test)
| Test Case | Input | Expected |
|---|---|---|
| Fresh start clears stale file | Write pipeline-tasks.md with issue #99, call initialize_state
|
File deleted |
| Resume with mismatched issue | Tasks file for #99, state file for #42, call resume_state
|
File deleted, warning logged |
| Resume with matching issue | Tasks file for #42, state file for #42, call resume_state
|
File preserved |
| Resume with malformed file | Tasks file with no - Issue: line, call resume_state
|
File deleted, warning logged |
| Build rejects stale injection | Tasks file for #99, GITHUB_ISSUE=#42, run build enrichment |
Tasks not injected, file deleted |
These are exercised by the test cases at scripts/sw-lib-pipeline-stages-test.sh:385–530.
- Liveness: N/A — CLI tool, not a service
- Readiness: N/A
- Functional: The five test scenarios above directly validate the bug fix against realistic data (task files with issue metadata, state files with frontmatter)
- Full
npm testsuite must pass — no new error patterns - The
pipeline-tasks.mdfile is in_GIT_BOOKKEEPING_FILES(helpers.sh:394), ensuring it is excluded from auto-commits and diff checks, so the cleanup operations don't trigger false "uncommitted changes" signals
PASS — All three defense layers are implemented and tested. The fix is minimal (no new files, no new dependencies) and follows the existing pattern of issue-based metadata validation already used elsewhere in the pipeline.