Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Pipeline Design 232

ezigus edited this page Mar 30, 2026 · 3 revisions

I now have a complete picture. The fix is already implemented — the code and tests are in place. Let me write the ADR.

Design: bug: pipeline-tasks.md not cleared on resume — stale tasks injected into build loop

Context

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:

  1. initialize_state() (scripts/lib/pipeline-state.sh:452) — called on fresh pipeline start
  2. resume_state() (scripts/lib/pipeline-state.sh:536) — called on shipwright pipeline resume
  3. 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.

Decision

Defense-in-depth at all three touchpoints — each layer independently prevents stale task injection:

  1. initialize_state() (line 463): Unconditionally deletes $TASKS_FILE on every new pipeline start. No issue comparison needed — a fresh run always starts clean.

  2. resume_state() (lines 607–620): After restoring state from pipeline-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).
  3. 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: and Issue: formats, case-insensitive

Edge cases handled:

  • TASKS_FILE variable unset or empty → [[ -n "${TASKS_FILE:-}" ]] guards in initialize_state; [[ -s "${TASKS_FILE:-}" ]] guards in resume_state and stage_build
  • GITHUB_ISSUE unset (goal-only pipeline, no issue) → current_issue will be empty; comparison "$tasks_issue" != "$current_issue" evaluates true only when current_issue is non-empty, so tasks are kept when there's no issue to compare against
  • File present but empty (0 bytes) → -s test fails, file is ignored

Alternatives Considered

  1. 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.

  2. 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.

  3. 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.

Implementation Plan

  • Files to create: none
  • Files to modify:
    • scripts/lib/pipeline-state.shinitialize_state() (line 463): add rm -f "$TASKS_FILE" ; resume_state() (lines 607–620): add issue-match validation block
    • scripts/lib/pipeline-stages-build.shstage_build() (lines 172–188): add issue-match guard before injection
    • scripts/lib/helpers.shextract_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_file grep pattern must match both - Issue: #42 and Issue: 42 formats — 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

Validation Criteria

  • initialize_state() deletes $TASKS_FILE unconditionally 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)

Smoke Test Specification

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.

Health Check Results

  • 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)

Regression Detection

  • Full npm test suite must pass — no new error patterns
  • The pipeline-tasks.md file 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

Issue Closure Decision

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.

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /