Skip to content

Resume a pane's CLI agent session after a restart - #14956

Open
bobrnor wants to merge 12 commits into
warpdotdev:masterfrom
bobrnor:lucky-sailing-harbor
Open

Resume a pane's CLI agent session after a restart#14956
bobrnor wants to merge 12 commits into
warpdotdev:masterfrom
bobrnor:lucky-sailing-harbor

Conversation

@bobrnor

@bobrnor bobrnor commented Aug 11, 2026

Copy link
Copy Markdown

Description

After a restart, a pane that had a CLI agent in the foreground comes back with that agent live on the same conversation instead of a bare shell.

Warp records, per pane, the agent kind, the session identifier the agent itself reported, an allowlisted set of resume-relevant flags from the user's own invocation, the directory, and when the state was last observed. At startup it resolves eligibility and runs the agent's own resume invocation. Panes it cannot resume safely come back exactly as they do today, silently.

Everything is behind FeatureFlag::AgentSessionResume, off by default, except two pieces that stand on their own: a precursor bug fix in session tracking, and the shell-history suppression (inert until a resume actually runs).

Design decisions worth knowing:

  • Resume by the agent's own session identifier, never by replaying the launch command. Re-running the launch command opens an empty conversation, which looks like restoration and is not.
  • The recording lives in its own pane-uuid-keyed table, on the blocks precedent, deliberately outside save_app_state's delete list. A row on a snapshot-rebuilt table such as terminal_panes would be erased by every full save — including the save that restore itself triggers per pane at shell bootstrap — so it could never survive to be read back. Two tests pin that, and both fail if the table is added to the delete list.
  • The resume invocation adds no flag of Warp's own. The three existing agent-invocation builders all attach approval bypasses unconditionally; this one must not, so it deliberately shares no code with them.
  • Every recorded value is untrusted. The store is a local database file any process running as the user can write, and the built string is parsed by an interactive shell. Each declared value shape is a character allowlist rather than a metacharacter denylist, a failing value drops its flag rather than being repaired, and every surviving value is single-quoted by a step that refuses a value containing a quote.
  • A resume is not a user command. It consumes none of the one-time state Warp reserves for a user's first command in a pane, and enters neither Warp's history nor the shell's history file.

Agent support is a data file. Only Claude and Codex are declared. Gemini's resume is unverified against a released CLI, and the Warp TUI reports a process-local entity id over OSC rather than the token its --resume accepts, so a resume built from what we record could never reattach. Adding an agent is an edit to app/resources/cli_agent_resume/agents.toml.

Linked Issue

Closes #14960.

  • The linked issue is labeled ready-to-spec or ready-to-implement. — Restore a pane's CLI agent session after restarting Warp #14960 was filed alongside this PR and has not been triaged yet. It is a feature request, so the readiness path runs through ready-to-spec and a spec PR; this branch is the implementation that exists behind an off-by-default flag, offered as evidence rather than as a shortcut past that step.
  • Screenshots or a short video of the implementation are included below. — a recording of a restart restoring a live agent session is owed; see the note under Testing.

Testing

Roughly 490 tests in the feature's scope pass, alongside the full workspace suite (11,066 passing; the 5 failures are pre-existing SSH integration tests that require the gcloud SDK and a remote host, and CI excludes them for fork PRs). cargo clippy --workspace --all-targets --tests -- -D warnings, cargo fmt --check, and ./script/check_no_inline_test_modules are clean. The migration was verified to apply as the last in the full chain and to revert cleanly, dropping only its own table.

Coverage worth calling out: hostile values (;, $(…), backtick, &&, |, *, newline, quote, leading dash, NUL, and more) each assert exact string equality with the bare invocation, so no fragment and no empty --flag '' residue can survive. A privacy test stuffs every conversation field with a marker, destructures the recorded struct exhaustively so a new field cannot be added without review, and asserts none of it is persisted.

  • I have manually tested my changes locally with ./script/run

Not manually tested, and this needs a human before the flag is promoted. The plan's rollout gates are deliberately unmet here: for every declared agent, confirm by hand that a resumed session performs no tool call and issues no model request before the first user message; measure cold-start time and memory on a profile with at least eight eligible agent panes; measure the capture-side write rate against a real agent session doing tool calls; confirm history suppression by hand in zsh, bash, PowerShell and fish; and pick R22's real freshness window from field data (the shipped 12 hours is a documented placeholder, and the telemetry age bands are deliberately edged at it so the distribution can answer what 6h or 24h would cost).

Review notes

A multi-reviewer pass over the finished feature found several things worth flagging, all fixed in the final commit:

  • Prompt text could become a permission grant. Flags were extracted by splitting the command on whitespace, so asking an agent to use --permission-mode bypassPermissions recorded that posture as if the user had chosen it, and the resume relaunched elevated. The justification for splitting on whitespace covered allowlisted values — all bare tokens — but not flag-shaped words inside a positional.
  • Capture ran with the feature flag off, so every user on a default build was recording agent state for a feature that cannot run.
  • A failed read of the new table aborted the entire restore, costing every window and tab, two lines below a comment promising housekeeping must never cost the user their session.
  • Fish users got no history suppression at all — the marker is matched by three bootstrap scripts and fish is not one of them. Fixed with the leading-space mechanism Warp already relies on for its own in-band commands, since fish omits leading-space commands from history as non-configurable default behavior.

One deliberate divergence from the plan, for the author to confirm: the plan directed carrying every allowlisted flag and explicitly accepted --mcp-config starting MCP servers unattended at startup. The shipped allowlist excludes --add-dir, --mcp-config and --settings. The first two are variadic, and after tokenisation a variadic flag's tail is indistinguishable from a trailing prompt positional, so carrying them would record the user's prompt as a directory. --settings names a file Warp cannot validate that can itself carry hooks and a permission mode. This is arguably the safer outcome but it is not what was specified.

Known gaps: a resume that fails at runtime has no direct test (it needs a real shell); the multi-pane case asserts each pane is armed with its own invocation rather than executed, because the restore harness has no live shell; and the wasm CI gate was not run locally — every import made unconditional here is already used unconditionally elsewhere in app/src, but CI is authoritative.

Separately: nothing in this repo enforces that crates/persistence/src/schema.rs is regenerated when crates/persistence/migrations/ changes. This branch briefly shipped a commit where they disagreed and the whole workspace failed to compile.

Screenshots / Videos

To be attached.

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

🤖 Generated with Claude Code

https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5

bobrnor and others added 12 commits August 11, 2026 22:19
…history

Extends the existing per-shell suppression that already excludes Warp's
in-band generator commands from the real history file so it also drops the
invocation Warp runs when restoring a pane onto an agent's previous session.

The three shells share one marker, `warp_resume_agent_session`, appended to
the invocation as a trailing comment: comment syntax is identical in zsh,
bash, and PowerShell, so the marker is inert to the shell, and it contains no
`:` that would break bash's HISTIGNORE separator. The leading-space route is
deliberately not used — Warp unsets hist_ignore_space and HISTCONTROL after
bootstrap so the user's own history options govern.

This is inert until a resume actually runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
Adds the cargo feature, the FeatureFlag variant, and the compile-time to
runtime bridge, registered in LOCAL_FLAGS and RUNTIME_FEATURE_FLAGS so the
whole agent-session-resume path is switchable at runtime and off by default.
Deliberately not added to the crate's `default` feature list.

Follows the FeatureFlag::LocalClaudeCodexChildHarnesses registration shape
across all five sites.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
A post-registration `session_start` was discarded outright, so a pane whose
user started a second conversation kept reporting the first conversation's
identifier. The CLI-agent footer already misattributes status because of it.

`CLIAgentSessionHandler::handle_event` now receives the identifier the session
currently holds, and `DefaultSessionListener` drops a `session_start` only when
it carries no identifier or repeats the recorded one. An event reporting a
genuinely new identifier reaches `update_from_event`, whose existing latch
adopts it without ever overwriting a known identifier with `None`.

Only the listener needed changing. The view's early return does not block the
event: the listener holds its own subscription to the same dispatcher and
receives every subsequent notification. Removing that early return would
process each post-registration event twice — duplicate `SessionUpdated`,
duplicate `CLIAgentPluginDetected` telemetry, and a re-fired rich-input
auto-open. `register_listener`'s existing-session path already adopts a newly
reported identifier and is not on this path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
…able

Adds an `agent_sessions` table keyed by pane uuid, holding the agent kind, the
identifier the agent itself reported, the resume-relevant flags, the directory,
and when the state was observed.

The table follows the `blocks` precedent and is deliberately absent from
`save_app_state`'s delete list. A row on a snapshot-rebuilt table such as
`terminal_panes` would be erased by every full save — including the save that
restore itself triggers per pane at shell bootstrap — so the value could never
survive to be read back. Two tests pin that guarantee, and both were shown to
fail when the table was added to the delete list.

The directory is recorded here rather than read back from the pane snapshot so
eligibility can later compare it against the directory the pane actually
restored into, without two columns claiming the same fact. It is stored as a
BLOB via the existing path encoding so non-UTF-8 paths survive.

`agent_kind` and `flags` are nullable on purpose: the writer degrades an
unserializable value to NULL rather than failing, and NOT NULL would turn that
degradation into a constraint error that aborts the whole snapshot transaction.
The read side treats either being absent or unparseable as "no recording"
rather than resuming from a half-known invocation.

`AgentSessionRestore` carries the loaded map plus an explicit startup-pass
flag, because `restore_pane_leaf` is also reachable from
`add_tab_with_pane_layout`; only `open_from_restored` sets it. Nothing emits
the write event yet and the restore call site only logs — capture, eligibility,
and launching are separate units.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
…ation

Adds an embedded TOML declaration of how each agent CLI reattaches to a prior
session, plus the builder, validator, and capture-side flag extractor that
read it. Adding an agent is a file edit; there is no per-agent restore path.

The declaration never names an executable — the binary comes from the detected
agent's `command_prefixes()` — and a key that tries to name one is a load
error. Two invocation shapes are modelled because both exist in the wild: a
flag taking the identifier (`claude --resume <id>`) and a subcommand taking it
(`codex resume <id>`).

Every recorded value is treated as untrusted: the store is a local database
file any process running as the user can write, and the built string is parsed
by an interactive shell. Two independent barriers keep it safe. Each declared
shape is a character allowlist rather than a metacharacter denylist, so
whitespace, `;`, `$`, backticks, globs, newlines, quotes and non-ASCII
lookalikes are all rejected by construction; a failing value drops its flag
rather than being repaired. Every surviving value is then unconditionally
single-quoted, and quoting refuses a value containing a single quote — the one
character that could end the wrapping. A resume pointer that fails its shape
yields no command at all rather than a partial line.

The builder adds no flag of its own, unlike the headless driver builders which
attach approval bypasses unconditionally. Flags are emitted before the
identifier so an agent taking a trailing prompt positional cannot swallow them.

Only Claude and Codex are declared. Gemini's resume is unverified against a
released CLI, and WarpTui reports a process-local entity id over OSC rather
than the token its `--resume` accepts, so a resume built from what we record
could never reattach. Claude's variadic `--add-dir` and `--mcp-config` are
excluded because a variadic flag is indistinguishable from a trailing prompt
positional once tokenized.

The built invocation carries the `warp_resume_agent_session` trailing-comment
marker the shell bootstrap files already suppress from history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
…ds anything

Adds the two-stage gate that decides, silently, which restored panes may
reattach to a recorded agent session.

Duplicate identifiers are resolved first, as a pure function over the loaded
pane-uuid map plus the window layout, before any window is created. Each window
is its own `add_window` call and the active one is created last, so a
first-wins-over-restore-order rule would hand a shared identifier to a
background pane. Claims rank by landing window, then visible window, active tab,
focused leaf, newest observation, and finally pane-uuid bytes as a stable
tie-break.

The per-leaf gate then returns a typed reason rather than a bool, so each
rejection is separately reportable. Rejection is always silent: no marker,
badge, message, or toast, because explaining a skipped resume would put a
Warp-specific artifact into a pane the user expects to look like their own
terminal.

Remoteness and shared-session viewing cannot be read from `CLIAgentSession` —
that is a live runtime struct which does not exist during startup restore, and
the recorded row carries no such column. The only restore-time evidence is the
snapshot itself: a cwd reaches it solely via `pwd_if_local`, and `input_config`
is left unset only by the viewer branches of `TerminalPane::snapshot`. Each
check names its source. Because the save path also drops the cwd for a local
pane whose directory vanished before the last save, that variant stands for
both unresumable cases; both restore as a shell, which is the acceptance
criterion.

Directory comparison canonicalizes both sides, since raw `PathBuf` equality
reports a false mismatch for symlinked paths such as macOS `/tmp` against
`/private/tmp`.

The claim check runs last so a pane that could not have resumed anyway does not
take the identifier from one that could.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
Wires the capture side: a per-pane subscriber, filtered on the terminal view id
it captured at subscription time, records the agent kind, the newest identifier
the agent reported, the allowlisted flags of the user's own invocation, the
directory, and when it was observed. `SaveAgentSession` becomes
`SetAgentSession { session: Option<..> }` so a save and a clear share one order
and one coalescing key.

Only allowlisted, alias-resolved flags are recorded — never the raw command
line, and never the session context. The capture path reads the sessions model
solely through a new accessor returning the agent and identifier, so there is
no reachable path to the user's prompts, the agent's replies, a summary, a tool
preview, or a draft. A test sets every one of those fields to a marker,
destructures the recorded struct exhaustively so a new field cannot be added
without review, and asserts none of it is persisted. Command text is read only
through the secrets-obfuscated accessor; obfuscated cells render as `*`, which
the declared value shapes reject at build time.

Ordering follows one total order: the send happens on the main thread from the
subscription callback rather than from independent spawned tasks, which have no
ordering guarantee and could land a newer identifier before an older one. Volume
is cut twice — unchanged observations are dropped at the source, and the writer
coalesces per pane uuid — because the session event fires once per tool call,
not once per turn, against a channel a blocking main-thread sender also uses.
Coalescing never hoists a snapshot past a capture write.

State is cleared only when the agent ends while its pane is still attached.
`detach` clears the attached flag before `remove_session` runs, so hidden-for-
close, closed, and teardown detaches are invisible to capture — which is what
keeps an undone close resumable. A suspended agent completes a background block
rather than a user block, so it never ends its session and keeps its state.

`resolve_command_aliases` is factored out of `CLIAgent::detect` unchanged,
because flag capture needs the alias-expanded text and `detect` returns only the
agent. Panes are not unsubscribed on detach: subscriptions are keyed by the
subscribing PaneGroup view, so unsubscribing one pane would kill its siblings'
capture.

`RecordedAgentSession.flags` becomes `Vec<RecordedFlag>` to match what the
extractor produces and the resume builder consumes, avoiding a lossy re-parse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
… command

An eligible restored pane now executes its agent's resume invocation instead of
coming up as a bare shell, without touching the user's input buffer, their
history, or the one-time state Warp reserves for a user's first command.

Three layers, because a command-source variant alone reaches almost nothing.
A new `AgentSessionResume` source carries `should_add_command_to_history:
false`, which gates both Warp's history and the persisted commands table, and
suppresses the arm that reports executed command text as telemetry. A block-level
`is_warp_authored` marker then covers everything that reads
`was_part_of_agent_interaction` — that value is derived from `ai_metadata` and is
structurally false for a resume, so a source variant cannot reach it.
`was_user_authored()` becomes the single choke point every consumer reads. The
shell's own history file is already handled by the bootstrap marker.

Three consumer classes needed the marker, not two. Passive suggestions have a
second live implementation beyond the one the plan listed, which would otherwise
have produced suggestions from a resume. The pane's zero-state affordance matches
on the block type with a wildcard rather than on agent metadata, so it is reachable
by neither the source nor the metadata; it needed the block marker specifically,
and gained the sibling test file it never had. The consent-banner site receives
only a block id, so authorship is threaded to it explicitly.

The invocation is held as data and dispatched from the existing shell-bootstrap
gate rather than staged in the user's editor: `set_pending_command` inserts
without clearing and then executes the whole buffer. The arm is taken before
execution, so a failure is left exactly as a terminal leaves any failed command
— no retry. A pane holding user text, a pane the user has typed into, and a pane
with a queued launch-config command all refuse the injection.

Permission-posture flags are carried only when the recording was observed within
a bounded window, now including the valued `--permission-mode`, not just boolean
bypass switches. A recording dated in the future is treated as stale, so winding
the clock back cannot revive an expired elevation. Past the window the pane still
resumes, just without the posture. The 12-hour value is provisional and
doc-commented as a rollout decision; a newly declared posture flag fails the
suite until it is acknowledged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
…s gone for good

Recorded rows are keyed by pane uuid and nothing removed them when a pane was
permanently discarded, so they accumulated for panes that will never return.

Deletion hangs off the same pane-lifecycle branch that already purges a gone
pane's per-uuid rows — `DetachType::Closed`, behind the same save-session guard —
rather than off `save_app_state`, whose snapshot transaction is exactly the
coupling the recorded state exists outside of. It is deliberately not folded into
`delete_blocks`, which is also reached from clearing a block list and must not
touch the agent row.

`Closed` is reached only from the undo-stack discard paths. Closing a tab or a
window detaches as `HiddenForClose`, so an undone close still owns its recording
and still resumes, and app teardown clears nothing.

This corrects a factual error in the capture unit's test: `clean_up_panes` is not
app teardown — teardown reaches `detach_panes` and hides for close — so that
assertion was pinning the opposite of what permanent removal requires. It now
lives with the permanent-removal case, and the hide-for-close assertion carries
the note that window close and teardown both take that path.

The load-time sweep is a secondary safeguard only: it drops rows for pane uuids
the saved snapshot does not restore, runs once per process before any pane
exists, and is non-fatal so a locked or read-only database still restores every
window.

`SetAgentSession { session: None }` is reused rather than adding an event, so
everything a pane says about its agent stays in one order and inherits the
existing per-pane coalescing — which is what stops a stale save from
resurrecting a deleted row. `detach` clears the attached flag first, so the
clear is always the pane's last word.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
…session

Adds `AgentSessionResume.PaneRestore.Outcome`, emitted once per restored pane
that carried recorded state, so the feature's field behavior is measurable
before the flag is promoted.

The payload is four closed values — agent kind, outcome, whether permission
posture flags were carried, and a coarse age band. No `String` reaches it at
all, so no invocation text, flag value, session identifier, or path can be
carried; `contains_ugc` is false. A test stuffs a recording with sensitive
markers, destructures the event exhaustively so a later field cannot slip
through unreviewed, and asserts the serialized payload contains no marker, no
`/`, and no `--`, with the key set pinned exactly.

A pane that was not running an agent reports nothing, structurally as well as by
outcome mapping — otherwise every ordinary pane would emit.

The age bands put R22's provisional 12-hour window on a band edge rather than
inside a band, so the distribution can answer what moving the window to 6h or
24h would cost rather than only how the guess did. Bands close at their upper
edge, matching the freshness rule itself, so the carrying population is exactly
the bands up to the window. A recording dated in the future gets its own band
instead of being folded in with the oldest, where it would read as false
evidence for a shorter window.

Posture-carried is reported only when the pane actually resumed: the posture is
computable for every pane, including ones that armed nothing, so reporting it
verbatim would claim flags were carried where nothing ran. The field means the
recording was fresh enough for its flags to ride along, not that flags existed
and survived validation — the builder drops invalid flags internally and the
call site cannot see which survived.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
Three pieces of waste found reviewing the feature, plus dead code.

The capture path did a grid walk with secret redaction, an alias resolution, a
flag extraction, and a working-directory stat on every firing of an event the
codebase documents as firing once per tool call — hundreds of times per agent
task — only for a same-value guard a few lines later to discard nearly all of
it. The pair naming the conversation holds for the whole task, so the burst now
settles on a map lookup and a short string compare, and the expensive read runs
only when that pair actually changes. The original guard stays as the final
check, since flags and directory can move while identity does not.

Startup paid `is_dir` and a canonicalization per restored pane with a recorded
session even when the feature flag was off — which is every user today. The
telemetry that follows is independently gated on the same flag, so with it off
that work had no observable effect at all. The gate now runs before the
filesystem work, and deliberately at the call site rather than inside the
eligibility function, so the unit tests that exercise the gate directly still
see real verdicts.

The restored directory was stat-ed twice: once by the caller filtering on
`is_dir`, then again inside the shared resolver. Verification and
canonicalization are now separate, so the recorded directory — whose existence
is genuinely unknown and is what R15 turns into an ineligibility reason — is
still checked, while the already-verified side is only canonicalized. Both sides
still canonicalize, which is what makes symlinked paths such as macOS `/tmp`
against `/private/tmp` compare equal.

`AgentSessionRestore::recorded_on_startup` is removed: it was superseded within
this branch by the eligibility gate, which cannot use it because it needs the
absent-recording and not-a-startup-pass cases as distinct outcomes that a single
collapsed `Option` cannot express. Nothing outside tests called it.

A doc block describing the command builder had drifted onto the neighbouring
posture-flag accessor; it is moved back, and that accessor now says why it
exists so it is not deleted as unused — it is what forces a newly declared
permission-posture flag to be acknowledged before it can ship.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
…tory gaps

Findings from a multi-reviewer pass over the whole feature.

Prompt text could become a permission grant. Flags were extracted by splitting
the command on whitespace, so flag-shaped words inside a quoted prompt — for
instance asking an agent to `use --permission-mode bypassPermissions` — were
recorded as if the user had chosen that posture, and the resume then relaunched
with an elevation they never asked for. The justification for splitting on
whitespace covered allowlisted *values*, which are all bare tokens; it did not
cover words in a positional. Tokenizing the way a shell does keeps a quoted
prompt one token, and extraction now stops at the first positional and at an
end-of-flags marker, so nothing past the prompt can be read as a flag.

`--settings` is no longer carried. Its value names a file Warp cannot validate
and that file can carry hooks and a permission mode, so replaying it started
unattended work at app startup that the posture freshness window does not
bound — the same reason `--add-dir` and `--mcp-config` are already excluded.

Capture was not behind the feature flag. Every user on a default build was
recording agent state for a feature that cannot run, so the gate now matches
the restore side, on both the write and the delete.

A failed read of the recordings aborted the whole restore. The `?` sat two
lines below a comment promising that housekeeping must never cost the user
their session, and it turned an unreadable optional table into the loss of
every window, tab and pane. It now degrades to no recordings, like the purge
above it.

Fish users got no suppression at all. The marker is matched by three bootstrap
scripts and fish is not one of them, so the invocation entered their history.
Fish omits leading-space commands from history as default, non-configurable
behavior, which is what Warp already relies on for its own in-band commands, so
the line is prefixed for that shell only. A test now asserts every shell that
suppresses Warp's own commands also suppresses this one — the coupling whose
absence let fish be missed.

Also: a claim winner that cannot resume no longer strands a conversation an
eligible pane could have taken; an internal invocation no longer raises a
user-facing toast; and three `AGENTS.md` violations are fixed — two doc comments
naming their callers, path qualifiers over imports, and a wildcard match arm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPDGcCMB9vFLwNJRMa3Z5
@cla-bot cla-bot Bot added the cla-signed label Aug 11, 2026
@oz-for-oss

oz-for-oss Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@bobrnor

Every PR must be linked to a same-repo issue before Oz can review it.

Next step: open or find a same-repo issue describing this change, then link it to this PR by adding Closes #123 to the PR description (or using the "Development" sidebar on GitHub). A maintainer will mark the issue ready-to-implement when it is ready. Once it is marked, comment /oz-review to re-trigger review.

See the contribution guidelines for the full readiness model.

Powered by Oz

@github-actions github-actions Bot added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label Aug 11, 2026

@oz-for-oss oz-for-oss 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.

@bobrnor

Every PR must be linked to a same-repo issue before Oz can review it.

Next step: open or find a same-repo issue describing this change, then link it to this PR by adding Closes #123 to the PR description (or using the "Development" sidebar on GitHub). A maintainer will mark the issue ready-to-implement when it is ready. Once it is marked, comment /oz-review to re-trigger review.

See the contribution guidelines for the full readiness model.

Powered by Oz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restore a pane's CLI agent session after restarting Warp

1 participant