Skip to content

fix(ship): resume preserved commit after timeout (sc-1550) - #386

Merged
norvalbv merged 1 commit into
mainfrom
codex/sc-1550-resume-preserved-ship
Aug 12, 2026
Merged

fix(ship): resume preserved commit after timeout (sc-1550)#386
norvalbv merged 1 commit into
mainfrom
codex/sc-1550-resume-preserved-ship

Conversation

@norvalbv

@norvalbv norvalbv commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Overview

Fixes Story #1550 by making an identical devkit ship retry resume the exact local commit preserved after a post-commit timeout or signal. Resume requires a ship-owned gate receipt plus matching base, normalized message, path scope, and current scoped tree; it pins the OID through push and uses atomic cleanup.

Direct relation to the ticket

The autonomous report records a first ship where the gated commit landed but the supervisor returned 124 before push, followed by the prescribed identical retry failing with branch already exists. This change moves that exact state from an unrecoverable collision to a verified continuation at push, PR creation, and manifest recording. Unrelated, changed, or hand-made commits remain rejected.

Regression proof

New integration coverage reproduces the report with a real hook that succeeds, lands the commit, leaks a pipe-holding descendant, and causes ship to return 124 with no remote branch. The identical retry must publish the preserved OID without rerunning the hook.

Without the fix:

bun run test:run cli/__tests__/ship-branch.test.mts -t "post-commit timeout|unrelated existing local branch"

failed at the retry with branch already exists: feat/post-commit-timeout (expected status 0, received 1).

With the fix:

  • focused recovery/adversary tests pass, including forged receipt rejection, Git-normalized message/path inputs, failed gate-log persistence, post-reap signal checkpointing, and concurrent branch cleanup
  • bun run test:run cli/__tests__/ship-branch.test.mts: 89/89 passed
  • bun run build, bun run lint, bun run typecheck, and bun run lint:structure: passed
  • full repository run: 3,796 passed and 5 skipped; two unrelated idle-pipe timing assertions failed only under parallel load, and their file passed 2/2 immediately in isolation

Summary by CodeRabbit

  • New Features

    • Added safer recovery for interrupted devkit ship operations, allowing verified retries when a commit was successfully created.
    • Improved handling of timeouts and signals during shipping without losing progress.
    • Added clearer guidance for shipping from linked worktrees and pull request creation.
  • Bug Fixes

    • Prevented unrelated or manually created branches and commits from being reused during retries.
    • Improved cleanup and branch protection when concurrent changes occur.
  • Documentation

    • Documented guard-size preflight, shipping rules, receipt verification, and recovery behavior.

## Overview

Fixes Story #1550 by making an identical `devkit ship` retry resume the exact local commit preserved after a post-commit timeout or signal. Resume requires a ship-owned gate receipt plus matching base, normalized message, path scope, and current scoped tree; it pins the OID through push and uses atomic cleanup.

## Direct relation to the ticket

The autonomous report records a first ship where the gated commit landed but the supervisor returned 124 before push, followed by the prescribed identical retry failing with `branch already exists`. This change moves that exact state from an unrecoverable collision to a verified continuation at push, PR creation, and manifest recording. Unrelated, changed, or hand-made commits remain rejected.

## Regression proof

New integration coverage reproduces the report with a real hook that succeeds, lands the commit, leaks a pipe-holding descendant, and causes ship to return 124 with no remote branch. The identical retry must publish the preserved OID without rerunning the hook.

Without the fix:

`bun run test:run cli/__tests__/ship-branch.test.mts -t "post-commit timeout|unrelated existing local branch"`

failed at the retry with `branch already exists: feat/post-commit-timeout` (expected status 0, received 1).

With the fix:

- focused recovery/adversary tests pass, including forged receipt rejection, Git-normalized message/path inputs, failed gate-log persistence, post-reap signal checkpointing, and concurrent branch cleanup
- `bun run test:run cli/__tests__/ship-branch.test.mts`: 89/89 passed
- `bun run build`, `bun run lint`, `bun run typecheck`, and `bun run lint:structure`: passed
- full repository run: 3,796 passed and 5 skipped; two unrelated idle-pipe timing assertions failed only under parallel load, and their file passed 2/2 immediately in isolation
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

devkit ship now supports receipt-verified retries for commits preserved after post-commit timeout or signal interruption. The flow validates branch state, commit metadata, scoped files, and tree integrity before publishing and cleaning up.

Changes

Ship recovery and retry flow

Layer / File(s) Summary
Recovery contract and operator guidance
.claude/skills/using-devkit/SKILL.md, .cursor/skills/using-devkit/SKILL.md, skills/using-devkit/SKILL.md, cli/commands/ship.mts, docs/decisions/..., .devkit/skills-manifest.json
Documentation and help text define branch creation, verified retries, receipt checks, timeout handling, and linked-worktree behavior.
Branch candidate validation and recovery
cli/lib/ship/ship-branch.sh
Existing branches are resumed only when their base, commit message, scoped changes, ship receipt, and tree match the requested ship.
Gate capture, publication, and cleanup
cli/lib/ship/ship-branch.sh, cli/lib/ship/review/process/gate-signal-handoff.sh
The flow defers signal exit during gate handling, records approved recovery receipts, pushes verified commits directly, and conditionally removes expected refs.
Recovery fixtures and integration coverage
cli/__tests__/_ship-branch-fixture.mts, cli/__tests__/ship-branch.test.mts
Shared fixtures and integration tests cover timeout and signal retries, receipt validation, publication, manifests, cleanup, concurrency, and rejection cases.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant ShipCLI
  participant GateSupervisor
  participant GitRemote
  Developer->>ShipCLI: run devkit ship
  ShipCLI->>GateSupervisor: execute gates and capture result
  GateSupervisor-->>ShipCLI: commit and recovery receipt
  ShipCLI->>GitRemote: publish branch and commit
  Developer->>ShipCLI: retry after timeout
  ShipCLI->>ShipCLI: validate receipt, branch, commit, and tree
  ShipCLI->>GitRemote: publish verified preserved commit
Loading

Possibly related PRs

  • norvalbv/devkit#10: Introduces related timeout and exit-124 recovery behavior in the gate-capture flow.
  • norvalbv/devkit#232: Adds related timeout and retry test infrastructure in ship-branch.test.mts.
  • norvalbv/devkit#354: Modifies the related ship recovery flow with commit-specific pre-push handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: resuming a preserved ship commit after a timeout.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/sc-1550-resume-preserved-ship

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
cli/__tests__/ship-branch.test.mts (1)

982-1000: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the scoped-tree mismatch rejection.

The suite covers receipt absence, unrelated branches, gate-log failure, and signal checkpointing. It does not cover the tree check at cli/lib/ship/ship-branch.sh lines 300-302. That check prevents publishing a preserved commit after the operator edits a scoped file between attempts, so it deserves a pinned test.

💚 Suggested test
it('rejects a preserved commit once the scoped files change', () => {
  const { dir, env, git } = seedShipRepoLocalRemote();
  const stubBin = ghStub('echo should-not-run; exit 9');
  const preserved = createPreservedCommit({
    dir,
    env,
    git,
    branch: 'feat/tree-drift',
    tempPrefix: 'ship-tree-drift-',
  });
  git(['update-ref', 'refs/devkit/ship-receipts/feat/tree-drift', preserved]);
  writeFileSync(join(dir, 'note.txt'), 'edited after the preserved commit\n');

  const retry = spawnSync('/bin/bash', [scriptPath, 'feat/tree-drift', 'ship it', 'note.txt'], {
    cwd: dir,
    input: 'pr body\n',
    encoding: 'utf8',
    env: { ...env, PATH: `${stubBin}:${env.PATH ?? process.env.PATH ?? ''}` },
  });

  expect(retry.status, retry.stderr).toBe(1);
  expect(retry.stderr).toContain('the current scoped files no longer match its commit');
  expect(retry.stdout).not.toContain('should-not-run');
  expect(git(['rev-parse', 'feat/tree-drift']).trim()).toBe(preserved);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/__tests__/ship-branch.test.mts` around lines 982 - 1000, Add a test
alongside the existing ship-branch rejection cases that uses
createPreservedCommit and the ship receipt ref, edits a scoped file afterward,
then retries via scriptPath. Assert the retry exits with status 1, reports that
the current scoped files no longer match the preserved commit, does not invoke
ghStub, and leaves the target branch pointing to the preserved commit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/__tests__/ship-branch.test.mts`:
- Around line 982-1000: Add a test alongside the existing ship-branch rejection
cases that uses createPreservedCommit and the ship receipt ref, edits a scoped
file afterward, then retries via scriptPath. Assert the retry exits with status
1, reports that the current scoped files no longer match the preserved commit,
does not invoke ghStub, and leaves the target branch pointing to the preserved
commit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a6c798a8-5296-4d6a-b13d-a6f2061107d6

📥 Commits

Reviewing files that changed from the base of the PR and between 4efe7ed and e19ba56.

⛔ Files ignored due to path filters (4)
  • dist/cli/commands/ship.mjs is excluded by !**/dist/**
  • dist/cli/lib/ship/review/process/gate-signal-handoff.sh is excluded by !**/dist/**
  • dist/cli/lib/ship/ship-branch.sh is excluded by !**/dist/**
  • dist/skills/using-devkit/SKILL.md is excluded by !**/dist/**
📒 Files selected for processing (10)
  • .claude/skills/using-devkit/SKILL.md
  • .cursor/skills/using-devkit/SKILL.md
  • .devkit/skills-manifest.json
  • cli/__tests__/_ship-branch-fixture.mts
  • cli/__tests__/ship-branch.test.mts
  • cli/commands/ship.mts
  • cli/lib/ship/review/process/gate-signal-handoff.sh
  • cli/lib/ship/ship-branch.sh
  • docs/decisions/ship-gates-converge-not-restart.md
  • skills/using-devkit/SKILL.md

@norvalbv
norvalbv merged commit 5ecc1c4 into main Aug 12, 2026
1 of 2 checks passed
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