Skip to content

feat: add post-PR lifecycle phases, ship method, and attempt tracking#63

Merged
stack72 merged 2 commits intomainfrom
post-pr-lifecycle-phases
Apr 9, 2026
Merged

feat: add post-PR lifecycle phases, ship method, and attempt tracking#63
stack72 merged 2 commits intomainfrom
post-pr-lifecycle-phases

Conversation

@stack72
Copy link
Copy Markdown
Contributor

@stack72 stack72 commented Apr 9, 2026

Summary

Port of systeminit/swamp#1152 and systeminit/swamp#1153 to the extensions repo.

  • Adds two new phases (pr_failed, releasing) between pr_open and done for visibility into CI failures and release builds
  • Adds three new methods: pr_merged (pr_open → releasing), pr_failed (pr_open → pr_failed), ship (releasing → done)
  • Adds pr-cooldown check enforcing a 3-minute wait after link_pr before checking PR status
  • Tracks PR attempt number across link/fail/merge cycles — lifecycle entries show numbered steps like plan iterations
  • Enables recovery from pr_failed via link_pr (re-link) or implement (major rework)
  • Adds skill guidance requiring human confirmation before opening PRs
  • Includes resumption and close-out guidance from swamp#1152

Test Plan

  • deno check — type checking passes
  • deno lint — no lint errors
  • deno fmt — all files formatted
  • All 38 tests pass (16 schema + 22 lifecycle)

🤖 Generated with Claude Code

stack72 and others added 2 commits April 9, 2026 14:16
…thod

Extends the issue-lifecycle model with intermediate states between PR open
and done, giving visibility into CI failures and release builds.

New methods: pr_merged (pr_open → releasing), pr_failed (pr_open → pr_failed),
ship (releasing → done). Adds pr-cooldown check enforcing 3-minute wait after
link_pr before checking PR status. Recovery paths allow link_pr and implement
from pr_failed. Skill guidance updated to require human confirmation before
opening PRs. Also includes resumption and close-out guidance from swamp#1152.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix plan-approved check to allow implement from pr_failed
- Add pr-cooldown check tests
- Add link_pr from pr_failed recovery test
- Track PR attempt number across link/fail/merge cycles
- Update link_pr description for pr_failed source phase
- Update start transition test to cover new phases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Blocking Issues

None.

Suggestions

  1. Schema description vs. implementation discrepancy for attempt: The PullRequestSchema describes attempt as "incremented on each subsequent link_pr call after a pr_failed cycle", but the implementation increments it on every link_pr call regardless — including URL corrections from pr_open. The test confirms this behavior (link_pr: is idempotent — second call increments attempt), so it is clearly intentional. Worth aligning the description to say "incremented on each subsequent link_pr call" to avoid confusion.

  2. Test fixture missing required attempt field: The pr-cooldown tests construct raw JSON without the required attempt field (e.g., { url: "...", linkedAt: "..." }). The tests still pass because the check only accesses pr.linkedAt and the data bypasses Zod validation via JSON.parse(...) as PullRequestData. Adding attempt: 1 to those fixtures would keep the test data consistent with the schema.

  3. Comment in test uses "backwards compat" for a new phase: schemas_test.ts describes complete accepting releasing as "backwards compat for new flow", but releasing is introduced in this very PR. "Fallback for the full flow" or just dropping the qualifier would be more accurate.

Otherwise the implementation is clean: all three new methods (pr_merged, pr_failed, ship) are correctly implemented and well-tested; the pr-cooldown check is properly gated on the right methods; env vars are restored in finally blocks; no any types in hand-written code; no live cloud services in tests; named exports only. The test coverage is comprehensive with 22 new lifecycle tests covering happy paths, error paths, and recovery flows.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial Review

Critical / High

None found.

Medium

  1. pr-cooldown check silently passes on corrupted linkedAt timestamps (issue-lifecycle/extensions/models/issue_lifecycle.ts:313-316)

    The cooldown computation is:

    const linkedAt = new Date(pr.linkedAt).getTime();
    const elapsed = now - linkedAt;
    if (elapsed < PR_COOLDOWN_MS) { /* reject */ }

    If pr.linkedAt is an invalid date string (data corruption, hand-edited record), getTime() returns NaN. Date.now() - NaN = NaN, and NaN < 180000 evaluates to false in JavaScript, so the check passes — the cooldown is silently bypassed.

    Breaking example: A pullRequest-main record with linkedAt: "not-a-date" would let pr_merged or pr_failed execute immediately after linking.

    Mitigating factor: linkedAt is always set by link_pr using new Date().toISOString(), so this requires external data corruption. Unlikely in practice.

    Suggested fix (if you want defense-in-depth):

    const linkedAt = new Date(pr.linkedAt).getTime();
    if (Number.isNaN(linkedAt)) {
      return { pass: false, errors: ["pullRequest linkedAt is invalid — re-link the PR."] };
    }

Low

  1. pr_merged re-execution after partial failure overwrites mergedAt (issue_lifecycle.ts:1287)

    If pr_merged succeeds writing the pullRequest resource but fails writing the state, a retry would re-read the PR (now containing mergedAt), then overwrite mergedAt with a new now (since args.mergedAt defaults to now). The original merge timestamp would be lost. In practice the timestamps would be seconds apart, so this is cosmetic.

  2. No escape from releasing on failed release builds (state machine design)

    If the release build fails after pr_merged, there's no method to go from releasing back to implementing or pr_open. The only exits are ship (done), complete (done), or start (destroys all progress). This is a design gap rather than a code bug — flagging for awareness.

Verdict

PASS — The state machine logic is well-structured, transition checks are consistent with the TRANSITIONS table, attempt tracking handles pre-upgrade records gracefully, and the test coverage is thorough (cooldown boundary cases, idempotent re-link, failure recovery paths). The NaN bypass in pr-cooldown is the only substantive finding and it requires data corruption to trigger.

@stack72 stack72 merged commit b03bc90 into main Apr 9, 2026
24 checks passed
@stack72 stack72 deleted the post-pr-lifecycle-phases branch April 9, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant