Skip to content

feat: remember agent cli options when resuming sessions - #2614

Closed
dhh wants to merge 3 commits into
herdrdev:masterfrom
omacom-io:upstream-remember-agent-launch-options
Closed

feat: remember agent cli options when resuming sessions#2614
dhh wants to merge 3 commits into
herdrdev:masterfrom
omacom-io:upstream-remember-agent-launch-options

Conversation

@dhh

@dhh dhh commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Native agent session restore rebuilds the resume command from the session reference alone, so a pane started as claude --permission-mode bypassPermissions comes back as plain claude --resume <id> with default permissions. The same applies to codex -s danger-full-access -a never and any other option a pane was started with.

Flagging the process point first: this touches persistence and resume behavior rather than being a focused bug fix, so per CONTRIBUTING.md it is the kind of change that wants maintainer alignment. Happy to move it to a Discussion if you would rather shape the approach before looking at code.

What changed

Capture — the foreground process probe already reads each process's argv, so when it identifies an agent it also extracts the command line after the agent token (detect::agent_launch_args_in_job, which handles wrapped invocations like node .../cli.js --flag). A new AgentLaunchArgsDetected event records it on the terminal, deduped so it fires once per change rather than once per probe. No new process inspection or filesystem work is added to the detection loop; it reuses the argv the probe already has.

Persist — a new optional agent_launch: {agent, args} field on the pane snapshot, stored per agent so a pane that later ran a different agent never resumes with the previous agent's options. Old snapshots deserialize with the field absent.

Replayagent_resume::plan_with_launch_args() appends the surviving options after the session reference. src/agent_launch_args.rs decides what survives, with two rules:

  • Options that select a conversation or a one-shot run are dropped, per agent (--resume, --continue, --session, --print, --prompt). Herdr supplies its own session reference.
  • A bare word is kept only when it directly follows a kept option, where it is that option's value. Bare words elsewhere are prompts, subcommands, or positional paths.

Everything else is replayed, including options Herdr has never heard of, so a new flag in a future agent release survives a resume without anyone touching Herdr.

The second rule is what makes the first cheap. The value of a dropped option disappears for free, because it is a bare word following no kept option: --resume <id> loses the id without Herdr knowing that --resume takes a value. The same falls out for agents that select a session through a subcommand and a positional id, so codex resume <id> --full-auto needs no special case. That means the only per-agent data is a list of option names, with no arity table to drift as the agent CLIs change.

It also stops a resume from compounding: after Herdr resumes a pane once, the live process argv contains the --resume <id> Herdr itself appended, and the next restore drops it before adding the current one.

Deliberate limits

  • An option given several separate values keeps the first: --add-dir /a /b resumes as --add-dir /a.
  • A prompt written directly after a flag cannot be told apart from that flag's value, so it is replayed. claude "fix the bug" and a prompt following a value are both dropped; claude --dangerously-skip-permissions "fix the bug" is not.

Both are documented in the module header and on the session-state page. Distinguishing those cases needs a per-agent table of every option and its arity; an earlier revision had one and it was not worth its weight.

Verification

cargo nextest run passes (3376 tests). cargo fmt --check is clean. Unit tests cover the argument rules, the argv extraction including wrapped invocations, the event recording, the snapshot capture, and the restore plan including the per-agent guard.

Also verified live in a throwaway session on a debug build, since unit tests do not cover the probe to snapshot to resume chain:

  • a pane agent started as claude --permission-mode bypassPermissions recorded agent_launch: {agent: "claude", args: ["--permission-mode", "bypassPermissions"]} in session.json
  • a snapshot seeded with session ref verify-session-id and args ["--model", "sonnet", "--resume", "stale-session", "some prompt"] resumed as hermes --resume verify-session-id --model sonnet — option replayed, stale session flag and its id dropped, trailing prompt left behind

Two notes on just ci from my machine: clippy -D warnings fails on two pre-existing lints in src/server/handoff.rs:381 and src/terminal_theme.rs:136, both files untouched by this change and both flagged only because my toolchain is newer than the pinned 1.96.1. integration-assets-test and plugin-marketplace-test need bun, which I do not have installed; neither covers this change.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 45d78862-d887-43bf-a319-0f9f2676ad41

📥 Commits

Reviewing files that changed from the base of the PR and between 29eba78 and 0766aa5.

📒 Files selected for processing (7)
  • docs/next/website/src/content/docs/session-state.mdx
  • src/agent_resume.rs
  • src/app/actions.rs
  • src/events.rs
  • src/main.rs
  • src/pane.rs
  • src/terminal/state.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/main.rs
  • src/events.rs
  • src/agent_resume.rs
  • src/terminal/state.rs
  • src/app/actions.rs
  • src/pane.rs

📝 Walkthrough

Walkthrough

The change captures agent startup arguments, stores them in terminal state and pane snapshots, filters incompatible options, and replays matching arguments when restored sessions start.

Changes

Agent launch replay

Layer / File(s) Summary
Replayable argument filtering
src/agent_launch_args.rs, src/agent_resume.rs
Resume planning appends filtered startup arguments. Session selectors and one-shot options are removed, while supported options and values are retained.
Argument detection and terminal state
src/events.rs, src/detect/mod.rs, src/pane.rs, src/app/actions.rs, src/terminal/state.rs, src/main.rs
Process probes detect agent arguments, publish deduplicated events, and store changed values on terminals.
Snapshot persistence and restoration
src/persist/snapshot.rs, src/persist/restore.rs, docs/next/website/src/content/docs/session-state.mdx
Pane snapshots store optional launch metadata. Restoration applies metadata only when the saved agent matches the restored session agent. Documentation describes replay rules and edge cases.

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

Sequence Diagram(s)

sequenceDiagram
  participant AgentProcess
  participant PaneDetection
  participant AppState
  participant Snapshot
  participant ResumePlanner
  AgentProcess->>PaneDetection: Provide foreground argv
  PaneDetection->>AppState: Send AgentLaunchArgsDetected
  AppState->>Snapshot: Store agent launch metadata
  Snapshot->>ResumePlanner: Provide matching saved metadata
  ResumePlanner->>ResumePlanner: Filter replayable options
  ResumePlanner-->>AppState: Return resume command
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.00% 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
Title check ✅ Passed The title clearly summarizes the main change: preserving agent CLI options when resuming sessions.
Description check ✅ Passed The description directly explains the capture, persistence, filtering, replay, limits, and verification for agent launch options.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@kangal-bot kangal-bot added the ai-review Trigger automated AI reviews for pull requests admitted by the PR gate label Aug 10, 2026

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/persist/restore.rs (1)

543-549: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Filter saved_agent_launch by agent match before storing it on the restored terminal.

restore_plan_for_snapshot filters launch_args by launch.agent == session.agent before building the resume argv, with a comment noting that a pane which later ran a different agent must not reuse the previous agent's options. This block does not apply the same filter: it stores saved_agent_launch onto the restored TerminalState even when saved_agent_launch.agent differs from the session's agent.

In the normal case the agents match, so this has no effect. If the pane later ran a different agent than the one whose options were last saved, and the server restarts again before the next process probe refreshes agent_launch_args, a subsequent snapshot could re-persist the mismatched agent's stale launch options.

🔧 Proposed fix to mirror the existing agent-match filter
             if let Some(plan) = pending_native_agent_restore {
                 let terminal_id = TerminalId::alloc();
                 let mut terminal = TerminalState::new(terminal_id.clone(), cwd.clone())
                     .with_pending_agent_resume_plan(plan);
-                if let Some(launch) = saved_agent_launch {
+                if let Some(launch) = saved_agent_launch.filter(|launch| {
+                    saved_agent_session.is_some_and(|session| session.agent == launch.agent)
+                }) {
                     terminal.set_agent_launch_args(&launch.agent, launch.args.clone());
                 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a589c0e1-c53d-416d-80c5-20b9fbb1a538

📥 Commits

Reviewing files that changed from the base of the PR and between 6c6ddcd and 63c13d9.

📒 Files selected for processing (11)
  • docs/next/website/src/content/docs/session-state.mdx
  • src/agent_launch_args.rs
  • src/agent_resume.rs
  • src/app/actions.rs
  • src/detect/mod.rs
  • src/events.rs
  • src/main.rs
  • src/pane.rs
  • src/persist/restore.rs
  • src/persist/snapshot.rs
  • src/terminal/state.rs

@dhh

dhh commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, fixed in 29eba78.

I went slightly wider than the suggested diff on two points:

  1. Same bug in a second place. The was_imported branch further down (restore.rs, the match runtime_result arm) stored saved_agent_launch unfiltered too. Fixing only the native-restore branch would have left that path re-persisting a mismatched agent's options.

  2. is_some_and would drop too much. The proposed saved_agent_session.is_some_and(...) discards the saved options whenever no session was recorded. That is harmless at the native-restore site, which only runs when a restore plan exists and therefore always has a session, but at the imported site a session is not guaranteed, so it would throw away options that nothing contradicts.

Both sites and restore_plan_for_snapshot now share one helper, agent_launch_for_session, which keeps the options unless a recorded session names a different agent. Covered by saved_agent_options_survive_only_a_matching_or_absent_session.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR captures agent CLI launch options, persists them with pane snapshots, and filters and replays them when restoring native agent sessions.

  • Extracts launch arguments from detected foreground processes, including wrapped agent invocations.
  • Stores launch arguments per agent in terminal and snapshot state.
  • Removes session-selection and one-shot arguments before constructing resume commands.
  • Adds documentation and tests for capture, persistence, filtering, and restoration.

Confidence Score: 4/5

The PR is not yet safe to merge because restored launch options can still be associated with a newer same-agent session when the first post-restore argv probe is unreadable.

The previously reported restored-options issue remains reachable: restored arguments live in TerminalState while detector-local history starts empty, and an unreadable first probe returns without clearing those arguments, allowing a later snapshot and resume to replay stale permissions, model, sandbox, or approval settings.

Files Needing Attention: src/pane.rs, src/persist/restore.rs, src/persist/snapshot.rs

Important Files Changed

Filename Overview
src/pane.rs Adds process-probe publication and deduplication for launch arguments, but the restored-state unreadable-argv path remains unresolved.
src/detect/mod.rs Extracts arguments following the detected agent token from foreground-process argv, including wrapped invocations.
src/terminal/state.rs Adds per-agent launch-argument state used by snapshot persistence and resume planning.
src/persist/snapshot.rs Serializes nonempty agent launch arguments into pane snapshots.
src/persist/restore.rs Restores matching per-agent launch arguments and supplies them to the resume planner.
src/agent_launch_args.rs Implements documented filtering of session selectors, one-shot options, positional arguments, and option values.
src/agent_resume.rs Extends native resume commands with filtered launch arguments.

Sequence Diagram

sequenceDiagram
  participant Probe as Foreground process probe
  participant Detector as Pane detector
  participant State as TerminalState
  participant Snapshot as Session snapshot
  participant Resume as Resume planner
  Probe->>Detector: agent identity + argv
  Detector->>State: AgentLaunchArgsDetected
  State->>Snapshot: persist agent and arguments
  Snapshot->>State: restore saved arguments
  State->>Resume: session reference + matching launch arguments
  Resume->>Resume: drop session/one-shot options
  Resume-->>State: agent resume command
Loading

Reviews (3): Last reviewed commit: "fix: drop agent options a later invocati..." | Re-trigger Greptile

Comment thread src/pane.rs Outdated
@dhh

dhh commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 2fa655c.

Confirmed: with unreadable argv the early return left the previous invocation's options in TerminalState, and since resume matches on the agent label alone, a later run of the same agent could resume with its predecessor's model, permission, sandbox, or approval flags.

The probe already knew which job it was looking at, so last_reported is now keyed on the tracked process group as well as the agent. Unreadable options are handled by which job is in front:

  • same job as the last report: a transient read failure, so the last options stand;
  • a different job: the stored options are its predecessor's, so empty options are published and nothing stale is replayed;
  • nothing reported yet: left alone, since anything stored came from a restored snapshot and is not the probe's to overwrite.

That last case matters — clearing unconditionally would have wiped the options this PR restores from the snapshot before the first successful probe.

Tests: unreadable_options_never_carry_across_agent_invocations and unreadable_options_leave_restored_ones_alone.

Comment thread src/pane.rs
dhh added 3 commits August 11, 2026 09:39
Native agent session restore rebuilt the resume command from the session
reference alone, so a pane started as `claude --permission-mode
bypassPermissions` came back with default permissions.

Capture the options the agent process was started with during foreground
process detection, persist them per agent in the session snapshot, and
replay them after the session reference on resume.

Two rules decide what survives. Options that select a conversation or a
one-shot run are dropped per agent, and a bare word is kept only when it
directly follows a kept option, where it is that option's value. The
second rule is what makes the first cheap: the value of a dropped option
disappears because it follows no kept option, so `--resume <id>` loses the
id without Herdr knowing that `--resume` takes one, and a subcommand form
such as `codex resume <id>` needs no special case. Everything else is
replayed, including options Herdr does not know about.
@dhh
dhh force-pushed the upstream-remember-agent-launch-options branch from 2fa655c to 0766aa5 Compare August 11, 2026 07:40
@ogulcancelik

Copy link
Copy Markdown
Collaborator

i'm working on a new agent resume manifest system which is going to supersede this, so closing for now.

@kangal-bot kangal-bot removed the ai-review Trigger automated AI reviews for pull requests admitted by the PR gate label Aug 12, 2026
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.

3 participants