Skip to content

trail resume: finish the non-interactive contract - #1884

Open
peyton-alt wants to merge 11 commits into
mainfrom
trail-resume
Open

trail resume: finish the non-interactive contract#1884
peyton-alt wants to merge 11 commits into
mainfrom
trail-resume

Conversation

@peyton-alt

@peyton-alt peyton-alt commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

https://entire.io/gh/entireio/cli/trails/961

entire trail resume was 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 --force to complete. The older-checkpoint confirmation (promptResumeFromOlderCheckpoint) fired without a CanPromptInteractively() 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; --force regains 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 via errors.As through SilentError, 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-resume keeps the full inspection dump.

--json action report. --json now composes with real resumes: trail, actions (fetched/switched, checkpoint id, commits behind HEAD), restored sessions, and a structured continuation (agent + session_id as fields). Typed failures emit the report with an error object and exit non-zero; pre-action failures keep empty stdout + stderr text. --no-resume --json is unchanged.

Idempotency pinned. Agents retry; a repeated resume is a clean no-op success, now guarded by an integration test.

entire session resume shares 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

  • Unit (7277), integration (380), and full Vogon canary all green
  • New unit tests pin the prompt-guard notice, typed-error round-trips, worktree-clash error, slim-output contract, and JSON report shape
  • Updated integration + e2e tests that previously pinned exit-0-on-nothing-resumed

🤖 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 shared entire session resume restore 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, and ResumeWorktreeClashError, recoverable via errors.As (including through SilentError). 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 --force just 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. --json works for real resumes (action report with continuation fields); typed failures include an error object in JSON. --no-resume --json stays the rich context dump.

Idempotency. New integration test TestResume_RerunIsIdempotent guards that repeating resume on the same branch stays a clean success.

Reviewed by Cursor Bugbot for commit 800b6d5. Configure here.

peyton-alt and others added 3 commits July 31, 2026 13:29
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
Copilot AI review requested due to automatic review settings July 31, 2026 18:34
@peyton-alt
peyton-alt requested a review from a team as a code owner July 31, 2026 18:34

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

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

Comment thread cmd/entire/cli/trail_resume_json.go
Comment thread cmd/entire/cli/trail_resume_json.go Outdated

Copilot AI 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.

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 --json act-path action report (including typed error objects on failure), while keeping --no-resume --json as 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.

Comment thread cmd/entire/cli/trail_resume_json.go Outdated
Comment thread cmd/entire/cli/trail_resume_cmd.go Outdated
peyton-alt and others added 8 commits July 31, 2026 15:02
- 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants