trail resume: finish the non-interactive contract - #1884
Conversation
Resume was designed interactive-first; for agent callers (the common case) the non-interactive path was never finished: - promptResumeFromOlderCheckpoint fired without a CanPromptInteractively guard, so any branch with commits newer than its last checkpoint hard-failed non-interactive runs unless --force was passed — teaching agents to reach for a flag that also overwrites local session logs. Non-interactive runs now proceed from the latest checkpoint with a one-line notice; --force regains its single meaning. - "Nothing was resumed" paths exited 0: the trail worktree clash, the no-checkpoint branch, and the three checkRemoteMetadata metadata-unavailable paths all printed guidance and returned nil. All now return errors so scripted callers can trust the exit code. - New typed sentinels (ResumeWorktreeClashError with the other worktree's path, ResumeNoCheckpointError, ResumeMetadataUnavailable- Error) survive SilentError wrapping via errors.As, giving orchestrators enough data to recover. Interactive behavior is unchanged; user-initiated declines still exit 0. `entire session resume` shares the restore engine and picks up the same guard and exit-code fixes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 2b8208def7de
The act path printed a trail header, checkpoint-session table, findings tables, and a menu of copyable commands before doing anything — a report bolted onto a verb, duplicating `trail show` and `trail finding` while costing agent callers context tokens on every run. Actually resuming now prints one identity line, the actions taken, and the default session's resume command as the final stdout line (bare, so scripted callers can lift it without parsing prose). Other restored sessions are named in a single pointer line. The removed sections take their fetches with them: the act path no longer calls the findings API, and the pre-restore checkpoint-session reads run only where they are consumed — the interactive multi-session picker and --session <id> resolution, which can target a checkpoint older than the latest. The inspection mode (--no-resume, with or without --json) keeps the full context dump; interactive prompts, the picker, and the decline-time command menu are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 5420d20e0738
--json was only legal with --no-resume, so an agent that actually performed a resume had no structured result. --json now composes with the act path and emits a compact action report: trail identity, actions taken (fetched/switched branch, checkpoint id, commits the checkpoint is behind HEAD), restored sessions, and a continuation whose agent and session id are separate fields so headless orchestrators can construct their own invocation instead of unquoting a command string. Failure contract: typed resume errors (worktree_clash with the other worktree's path, no_checkpoint, metadata_unavailable) emit the report with an error object on stdout and exit non-zero; pre-action failures (auth, validation, assertions, uncommitted changes) keep the default empty-stdout/stderr-text behavior, so callers parse stdout only when it is non-empty. --no-resume --json keeps the inspection payload unchanged. Re-running resume is pinned as an idempotent success by an integration test: agents retry, and the second run must keep the restored log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 5aed8bd7ef09
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 800b6d5. Configure here.
There was a problem hiding this comment.
Pull request overview
This PR completes the non-interactive contract for entire trail resume (and the shared entire session resume restore path) by making non-interactive runs never block on prompts, returning non-zero when nothing is resumed, adding typed resume errors, and introducing a --json action report for the act path.
Changes:
- Make resume “honest” for automation: non-interactive older-checkpoint confirmation is guarded, and “nothing resumed” paths now return typed errors (non-zero exit).
- Add
trail resume --jsonact-path action report (including typed error objects on failure), while keeping--no-resume --jsonas the richer inspection payload. - Update/expand unit, integration, and E2E tests to pin the new output and exit-code semantics (including idempotent reruns).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| e2e/tests/resume_test.go | Updates E2E expectations to require non-zero exit when no checkpoint exists. |
| cmd/entire/cli/trail_resume_output_test.go | Adds unit tests pinning the slim non-interactive stdout contract (final line is bare resume command). |
| cmd/entire/cli/trail_resume_json.go | Introduces --json act-path report generation, typed-error mapping, and best-effort “actions” fields. |
| cmd/entire/cli/trail_resume_json_test.go | Adds unit tests for JSON report shape, preferred-session selection, and typed-error mapping. |
| cmd/entire/cli/trail_resume_cmd.go | Reworks trail resume act vs inspection paths; adds identity line, slim continuation display, JSON act path, and typed worktree-clash error. |
| cmd/entire/cli/trail_resume_cmd_test.go | Updates option validation tests to allow --json without --no-resume. |
| cmd/entire/cli/resume.go | Adds prompt guard for non-interactive older-checkpoint cases; returns typed errors for no-checkpoint and metadata-unavailable paths. |
| cmd/entire/cli/resume_test.go | Updates unit tests to assert typed errors (no checkpoint, metadata unavailable). |
| cmd/entire/cli/resume_errors.go | Adds typed resume error types (ResumeWorktreeClashError, ResumeNoCheckpointError, ResumeMetadataUnavailableError). |
| cmd/entire/cli/resume_errors_test.go | Adds tests ensuring typed errors survive wrapping and that key failure paths return typed errors. |
| cmd/entire/cli/integration_test/resume_test.go | Updates integration tests for new non-zero exit semantics and adds idempotency coverage. |
| cmd/entire/cli/git_operations_test.go | Minor test update to reuse shared test branch constant. |
- The older-checkpoint confirmation was gated only on terminal interactivity, so --json at a real TTY without --force could still pop the huh dialog (with its explanation discarded), breaking the stdout-is-only-JSON contract. restoreFromCurrentBranch gains an allowPrompts parameter the JSON driver sets to false — decoupled from --force, which keeps its log-overwrite meaning. - --session <id> --json with an id not among the restored sessions silently fell back to the default session with exit 0. It now fails with the same "not found in the restored checkpoint" error as the human path, so the continuation can never point at a different session than requested. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 5fddcb8343bf
- session_not_found joins the typed-error enum: a bogus --session id now routes through the JSON failure report (carrying the session id and the side effects that already happened — branch switch, restored sessions) instead of a bare stderr error with empty stdout after real state changes. The shared human path returns the same typed error. - switched_branch no longer lies on detached HEAD: a failed current-branch read means there was no branch, so a successful checkout counts as a switch. - Worktree-clash guidance in --json mode goes to stderr instead of being discarded; stdout stays pure JSON. - The action report encoder disables HTML escaping, matching jsonutil. - Help text scopes the exit-code promise to non-interactive/--json runs (interactive declines exit zero by design). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 4c4d29939ac4
Two trail-review findings on the JSON act path: - checkpoint_behind_head described the branch's latest checkpoint even when --checkpoint/--session resumed an older one, where the count misleads. It is now only reported on the default latest-checkpoint path. - A checkpoint whose restore produces zero sessions (session log content unavailable) emitted a success report with empty restored_sessions and exit 0. Both the JSON path and the human non-interactive act path now fail with a typed no_sessions_restored error; interactive zero stays exit 0 (the user declined a prompt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: d07a949e3857
Entire-Checkpoint: 01KZ4B7B7N371VKMFA2JP7BVCY
…contract Post-merge fixups: main's new auto-resolving `checkpoint resume` tests pinned exit 0 on nothing-resumed (unreadable metadata, branch without checkpoints), which this branch's contract makes typed failures. The delegation each test proves (checkpoint-before-branch, branch checkout, remote-branch fallback) is asserted unchanged; only the exit expectation flips. Also de-duplicates a "trail" literal goconst hit introduced by the merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KZ4C0FPZT0529ERYGXTSW874
Five-agent review of the grown PR surfaced three behavior holes and a coverage gap, all fixed: - --session no longer falls back to restoring the latest checkpoint when the session-context read fails or the id is unknown. The fallback ran the restore BEFORE the membership check, so with --force it overwrote session logs the caller never asked to touch; both paths (human and --json) now fail before any restore. - The shared zero-sessions guard moves into the engine (ensureSessionsRestored) and now covers `entire session resume` and `entire checkpoint resume`, which could still exit 0 when a resolved checkpoint restored nothing (transcript unavailable). Interactive declines keep exit 0; --force counts as non-interactive. - JSON/text parity on continuation resolution: an unresolvable agent errored on the text path but emitted exit-0 success with an empty command in --json. The builder now propagates the error. - checkpoint resume's worktree clash — the exact condition this PR typed — exits non-zero with ResumeWorktreeClashError; guidance moves to stderr, as it now does on all act paths so stdout stays liftable. - runTrailResumeJSON gets its first end-to-end test (success and typed failure against a repo fixture, stdout parsed as JSON both ways), plus the --force zero-sessions case and unresolvable-agent builder case. Failure reports record a requested --checkpoint id, swallowed best-effort errors now log at debug, and the encode-failure path preserves the original typed error. - Docs: stranded prompt doc comment restored, metadata-error and ensure-guard comments no longer overstate, help text names the trail branch (not "the checkpoint") for the picker, resume notices stop borrowing status.go's detached-HEAD constant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KZ4DX8E4MMY3BBM1WAKYDSDY
Comments explaining why a fix is correct belong in the commit messages that introduced them; the files keep only the constraints the code cannot show. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KZ4EZWMJ4FXP85VK72XTZYH0
Risk-monitor follow-ups: switched_branch and fetched_branch were the report's only unpinned fields — two driving tests now cover the checked-out-from-elsewhere and remote-only-branch cases end to end. CLAUDE.md's session bullet documents the non-interactive exit-code contract and the --json action report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 01KZ4MWNET0N4ND8R6M8R7BX5H

https://entire.io/gh/entireio/cli/trails/961
entire trail resumewas designed interactive-first; for agent callers (the most common caller) the non-interactive path was never finished. This PR completes it without touching the core semantics — checkpoint discovery, restore, and interactive launch all stay as they are.Breaking for scripts: resume commands (
trail resume,session resume,checkpoint resume) now exit non-zero whenever nothing was resumed; callers that relied on exit 0 in those cases must update. Interactive prompt declines still exit zero.What changed
Never block, never need
--forceto complete. The older-checkpoint confirmation (promptResumeFromOlderCheckpoint) fired without aCanPromptInteractively()guard, so any branch with commits newer than its last checkpoint hard-failed non-interactive runs — teaching agents to always pass--force, a flag that also overwrites local session logs. Non-interactive runs now proceed from the latest checkpoint with a one-line notice;--forceregains its single meaning.Honest exit codes. Every "nothing was resumed" path exited 0: the worktree clash, the no-checkpoint branch, and three metadata-unavailable paths in
checkRemoteMetadata. All now return errors. Interactive user declines remain exit 0 by design (unreachable for agents).Typed errors.
ResumeWorktreeClashError(carries the other worktree's path),ResumeNoCheckpointError,ResumeMetadataUnavailableError— recoverable viaerrors.AsthroughSilentError, so orchestrators can act on failures instead of parsing stderr.Slim act path. Actually resuming no longer prints the trail header, session table, findings tables, and command menu (that's
trail show/trail finding's job — and the act path skips those fetches entirely). Output is now: identity line, actions taken, and the default session's resume command as the final stdout line, machine-liftable.--no-resumekeeps the full inspection dump.--jsonaction report.--jsonnow composes with real resumes: trail, actions (fetched/switched, checkpoint id, commits behind HEAD), restored sessions, and a structured continuation (agent+session_idas fields). Typed failures emit the report with anerrorobject and exit non-zero; pre-action failures keep empty stdout + stderr text.--no-resume --jsonis unchanged.Idempotency pinned. Agents retry; a repeated resume is a clean no-op success, now guarded by an integration test.
entire session resumeshares the restore engine and picks up the prompt guard and exit-code fixes; its tests are updated accordingly.Companion PR (independent, also off main): trail show --json.
Testing
🤖 Generated with Claude Code
Note
Medium Risk
Changes CLI exit semantics and resume output for automation; core restore logic is mostly unchanged but agents and scripts may depend on the old exit-0-on-partial-failure behavior.
Overview
Completes the non-interactive contract for
entire trail resume(and sharedentire session resumerestore paths) so agent callers get predictable behavior without leaning on--force.Exit codes and typed failures. Paths that previously returned success with nothing restored now fail with
ResumeNoCheckpointError,ResumeMetadataUnavailableError, andResumeWorktreeClashError, recoverable viaerrors.As(including throughSilentError). Integration and e2e tests now expect non-zero when no checkpoint exists or metadata cannot be loaded.Non-interactive older checkpoints. When HEAD is ahead of the last checkpoint and there is no TTY, resume proceeds with a one-line notice instead of blocking on
promptResumeFromOlderCheckpoint, so agents do not need--forcejust to avoid prompts.Trail resume UX. The act path drops the full inspection dump (sessions/findings/menus); it prints a short identity line, restore summary, and the default session’s resume command as the final stdout line.
--jsonworks for real resumes (action report with continuation fields); typed failures include anerrorobject in JSON.--no-resume --jsonstays the rich context dump.Idempotency. New integration test
TestResume_RerunIsIdempotentguards that repeating resume on the same branch stays a clean success.Reviewed by Cursor Bugbot for commit 800b6d5. Configure here.