Skip to content

[Bug]: Cleanly exited Claude session is auto-resumed with claude --resume after an app restart (local macOS, hibernation off) #14228

Description

@congara

Operating system

macOS

Orca version

1.4.179 (arm64)

Details

Short summary: A Claude Code session that I ended deliberately — exit inside Claude, then exit in the shell, so both the agent and the terminal tab are gone — is auto-resumed with claude --resume <old-session-id> the next time I quit and reopen Orca. It comes back as if the session had crashed or been interrupted, even though I closed it cleanly. This is a purely local macOS workspace: no SSH target, no relay, no remote runtime, and experimental Agent Hibernation is off.

How can we reproduce it?

  1. macOS, local project (no SSH/remote runtime). Default TUI agent = claude. Settings → experimental Agent Hibernation off.
  2. Open a terminal tab in the project, run claude, do some work in the session.
  3. Type exit inside Claude → back at the shell prompt, the claude process is gone.
  4. Type exit in the shell → the terminal tab closes on its own.
  5. Quit Orca (⌘Q) and relaunch it.
  6. The project reopens with a terminal tab already running claude --resume <the session id from step 2>.

Expected behavior

A session the user ended on purpose should not be a restore candidate. Closing Claude and its tab should retire the pane's agent record (prune it / write a close tombstone), so that only sessions that were still alive when Orca quit are resumed on relaunch. A discoverable setting for "restore agent sessions on launch" would help as a second-line fix (#12250, #13633), but the primary problem here is that a clean, user-initiated exit is treated as an unexpected termination.

What I can see in the persisted state

Profile store: ~/Library/Application Support/Orca/profiles/local-default/orca-data.json

  • workspaceSession.sleepingAgentSessionsByPaneKey holds one record per pane:

    {
      "paneKey": "<tabId>:<paneId>",
      "worktreeId": "<repoId>::/Users/…/<project>",
      "agent": "claude",
      "providerSession": { "key": "session_id", "id": "<uuid>", "transcriptPath": "/Users/…/.claude/projects/…/<uuid>.jsonl" },
      "prompt": "<my last prompt, truncated>",
      "state": "working",
      "origin": "live",
      "capturedAt": 1786603058659,
      "updatedAt": 1786603041327
    }
  • workspaceSession.terminalSurfaceTombstonesByPaneKey is {} on my install — it has never been populated, including for the panes I closed with exit.

  • Records outlive their panes. In a rotated backup written at Aug 12 08:13 (orca-data.json.bak.0…4), the only record still carried state: "working" with capturedAt from Aug 11 18:00 — a session from the previous day.

  • Across all six snapshots I have (5 backups + current), every record has state: "working" and origin: "live". I never see a record flip to done or get removed.

  • settings.experimentalAgentHibernation is false, so this restore is not coming from the hibernation feature path.

  • sshTargets: [], sshRemotePtyLeases: [] — nothing remote is involved.

Hypothesis (not verified — I only have the packaged app)

exit inside Claude does not produce a terminating status update, so the record's last known state stays "working" forever. #11598 documents the activation-time validation as discarding a record when state !== "done" && capturedAt - updatedAt > 30 min; in my record capturedAt - updatedAt is ~17 s, so a stale-but-working record passes validation and gets resumed. If that's right, the missing piece is retiring the record (or writing a tombstone) when the agent process exits normally / the pane is closed — not the staleness window.

Related, but not the same issue

Happy to pull more state or logs from my install if that helps narrow it.

Activity

  1. erishforG commented on Aug 14, 2026

    @erishforG
    Contributor

    I reproduced this on current main and traced it to resume authority surviving a confirmed agent exit. I opened #14534 with a focused fix.

    A bare PTY exit intentionally preserves the sleeping-session record for crash recovery. However, Orca already emits an agent-exited fact only after foreground-process verification confirms that Claude has returned to the shell. The PR clears only that pane resume record at this confirmed transition, while preserving sibling panes and unexpected-PTY-loss recovery.

    Regression coverage includes a confirmed agent exit followed immediately by PTY teardown, exact-pane isolation, and the existing crash-recovery behavior. I also manually verified the full macOS flow: exit Claude, exit the shell, restart Orca, and confirm that no Claude resume process is started.

  2. erishforG commented on Aug 14, 2026

    @erishforG
    Contributor

    The fix is ready in #14534 and has been verified with focused regression coverage plus a macOS exit-and-restart recording. It preserves unexpected PTY-loss recovery while retiring resume state only after a process-confirmed agent exit.

    @AmethystLiang, when you have time, could you please take a look? Thank you.

  3. erishforG commented on Aug 17, 2026

    @erishforG
    Contributor

    @OrcaWin @AmethystLiang following up on PR #14534 — it conflicted with the pty-connection test file being split into ~50 focused files, so I moved the regression test into pty-connection-main-side-effect-authority.test.ts where it belongs, and rebased clean. CodeRabbit also caught that the ordering assertion only checked the first of two cleanup calls — fixed that too. This is P0 but the PR is labeled P1, flagging in case it's a triage gap.

  4. shay2911 commented on Aug 19, 2026

    @shay2911

    Confirmed on local macOS after updating to Orca 1.4.185, with Agent Hibernation not involved.

    The persisted profile had 8 terminal tabs immediately before the update. The first post-update snapshot had 16. The updater trace shows the normal 1.4.185 quit/install/relaunch sequence; during renderer startup the daemon created 9 PTYs in the same second.

    The pre-update session contained 8 sleepingAgentSessionsByPaneKey records whose original tab IDs were absent from tabsByWorktree (6 Claude, 2 OMP; all origin: "live", still working/waiting). Relaunch converted those into 8 replacement agent tabs, plus one normal shell, explaining the exact 8 → 16 jump. Provider-session correlation confirmed that the replacement Claude tabs reused the orphan records' sessions rather than representing unrelated launches.

    After closing the resurrected tabs, the live profile still contained 8 orphan working/live resume records, so another restart could repeat the behavior. No profile values, prompts, tab titles, paths, or provider-session IDs are included here.

    This is strong independent confirmation of the mechanism described in this issue and of the need for #14534's process-confirmed exit cleanup.

  5. LLLLLayer commented on Aug 24, 2026

    @LLLLLayer

    Still reproducing on 1.4.188 (arm64), local macOS, Agent Hibernation off, no SSH/remote runtime. #14534 is still open, so this is expected — but while digging into my own install I read the packaged renderer bundle, and two details contradict / extend the hypothesis in the original report. Both matter for whether the fix in #14534 is sufficient.

    (Identifiers below are from the beautified app.asar bundle of 1.4.188 — names may differ from source. No paths, prompts, tab titles, or provider-session IDs included.)

    1. The record is not merely left stale — it is actively rewritten back to working

    The report's hypothesis is that "exit inside Claude does not produce a terminating status update, so the record's last known state stays working forever." That's not what the code does. In setAgentStatus (out/renderer/assets/store-*.js), a done status for a resumable agent takes a dedicated branch that rewrites the recovery record as working:

    const retainsResumableRecoveryIdentity =
      payload.state === "done" &&
      isResumableTuiAgent(identity.agentType) &&
      providerSession !== void 0 &&
      getAgentResumeArgv(identity.agentType, providerSession) !== null;
    
    const liveRecoveryWorktreeId =
      entry.state === "done" && !retainsResumableRecoveryIdentity
        ? null
        : entry.worktreeId ?? findAgentPaneWorktreeId(s, entry.paneKey);
    
    const liveRecoveryRecord = liveRecoveryWorktreeId
      ? sleepingRecordFromEntry({
          state: s,
          entry: retainsResumableRecoveryIdentity
            ? { ...entry, state: "working", prompt: "", lastAssistantMessage: void 0 }  // <-- here
            : entry,
          worktreeId: liveRecoveryWorktreeId,
          capturedAt: updatedAt,
          launchConfig: launchConfigSource,
          origin: "live",
        })
      : null;

    So even when Orca does observe the done transition cleanly, the pane keeps a state: "working" / origin: "live" resume candidate. The prompt: "" + lastAssistantMessage: undefined shape is a reliable fingerprint for these in a profile dump — I have records in that exact shape sitting next to genuine mid-work captures.

    This looks intentional (keep the session identity so the pane can be resumed later), but it means "agent finished normally" and "agent was interrupted" are indistinguishable in the persisted record.

    2. The staleness check is structurally dead for these records, not just narrowly missed

    function isInvalidWorktreeActivationRecord(record) {
      if (!record.origin && record.state === "done") return true;
      return record.state !== "done" && record.capturedAt - record.updatedAt > 18e5;
    }

    The original report notes its own record was "~17 s" and therefore passed. It's stronger than that: for every record produced by the live-recovery path above, the difference is exactly 0 by construction. setAgentStatus computes one updatedAt, puts it on entry.updatedAt, and passes the same value as capturedAt; sleepingRecordFromEntry then copies updatedAt: args.entry.updatedAt. All records in my profile have capturedAt === updatedAt to the millisecond.

    There is no comparison against now anywhere in this predicate, so age is irrelevant — I am carrying records captured 3–4 days ago that are still valid resume candidates today, unchanged across five rotated orca-data.json.bak.* snapshots.

    3. A second trigger path: cold restore resumes in place, without the tab ever disappearing

    The report describes pane/tab gone → resumeSleepingAgentSessionsForWorktree → launchSleepingAgentSession → new tab. There is a second route that produces no new tab, so it's easy to miss when counting tabs:

    If the pane and its split layout survive the restart but the PTY does not, buildColdRestoreAgentResumeStartup() (out/renderer/assets/OnboardingInlineCommandTerminal-*.js) reads the same sleeping record and re-runs claude --resume <session-id> into the original pane, then shows the "session restored" banner. My last restart hit exactly this: a session whose transcript had no new content for three days had its .jsonl touched 15 s after app start, in the same tabId:leafId it originally ran in. Tab count never changed.

    Both routes read the same sleepingAgentSessionsByPaneKey entry, so a fix at the record level covers both — but repro steps that only check for extra tabs will miss this variant.

    Implication for #14534

    Clearing the resume record on a process-confirmed agent exit is the right primary fix and would cover the reported repro. Two gaps look like they'd survive it, though:

    • Anything that ends the agent without a confirmable exit transition — SIGKILL, force-quit / crash of Orca, host reboot, or the status hook simply not firing — still leaves a working/live record that is a permanent resume candidate, because of (1).
    • Because of (2), there is no backstop that ever retires such a record. An absolute age bound (Date.now() - record.capturedAt against some window) as defense-in-depth would bound the blast radius of any missed exit signal, independent of exit detection.

    Separately, and consistent with #12250 / #13633: I went through getDefaultSettings() in the main bundle, every ORCA_* variable referenced by the main-process bundle, and the Settings panes — there is no user-facing switch for this, and no internal escape hatch either. The only reachable kill switch is Settings → Agents → Agent status hooks, which works only as a side effect (no hooks → no providerSession → sleepingRecordFromEntry returns null → no record), at the cost of all agent status, notifications, and auto-titles. Worth noting as evidence that the toggle asked for in #13633 has no workaround today.

    Happy to provide more structured detail from my profile if useful.

  6. guo commented on Aug 27, 2026

    @guo

    Still present on 1.4.188 (arm64), local macOS, Agent Hibernation off, no SSH/remote runtime.

    Adding four things I did not find in the thread: how far the accumulation actually goes on a busy install, a severity multiplier nobody has raised, one property of the age bound @LLLLLayer proposed that I think justifies it as its own change rather than a follow-up to #14534, and a small correction.

    (Counts and code only — no paths, project names, tab titles, prompts, or provider-session IDs.)

    1. The accumulation is unbounded in practice, not only in theory

    Snapshot of one install running a large parallel-agent fleet:

    sleepingAgentSessionsByPaneKey records 24
    live panes (orca terminal list) 32
    records whose pane no longer exists — i.e. resume candidates 9
    oldest such record 235 h (9.8 days)
    age range of those 9 47 h – 235 h
    capturedAt - updatedAt across all 24 records 0.0 – 0.2 min

    All 9 pass isInvalidWorktreeActivationRecord. The guard reads 0.0 min on a record that is 9.8 days old, which is @LLLLLayer's point restated as a measurement: the predicate is not narrowly missing these, it is inert for them.

    Nothing retires them. They persist across restarts and accumulate per worktree, so the count tracks how many worktrees you have touched rather than converging.

    2. Severity multiplier: a resurrected session inherits the yolo defaults

    This is the part that I think changes the priority calculus, and I have not seen it raised.

    YOLO_TUI_AGENT_ARGS in the main bundle maps claude (and claude-agent-teams, openclaude, antigravity) to --dangerously-skip-permissions, codex to --dangerously-bypass-approvals-and-sandbox, gemini to --yolo. Installs carrying settings.agentYoloDefaultsMigrated have those applied as their default args.

    So the failure mode is not only "an unexpected tab appears". It is: a session last touched 9 days ago relaunches unattended, with permission prompts bypassed, in a worktree whose branch, working tree and dependencies have all moved since that session last had context. Its first action after --resume lands on a repo state it has never observed, without a prompt gate.

    3. The age bound is self-healing — it repairs existing installs, not just new ones

    Worth spelling out, because it is an argument for shipping the bound independently rather than as a follow-up. When the predicate returns true the caller does not merely skip the launch:

    if (isInvalidWorktreeActivationRecord(record)) {
      state.clearSleepingAgentSession(record.paneKey)
      continue
    }

    It deletes the record. So an absolute bound (Date.now() - record.capturedAt > window) garbage-collects every already-accumulated orphan at the next worktree activation: no migration, no user action, no profile editing — and specifically no app restart, which is the one remedy unavailable to anyone with a live fleet running.

    It also covers the class that exit detection structurally cannot. SIGKILL, force-quit, host reboot, or a status hook that simply does not fire all end the agent without emitting an exit transition to observe; you cannot detect an event that was never emitted. Only elapsed time bounds that class. #14534 and an age bound are complementary and touch different files.

    Given the existing "an interrupted turn is still resumable" intent in that function, the window presumably wants to be generous — days, not hours. The asymmetry favours it: if the bound fires early the user loses only automatic resume, while the transcript and a manual claude --resume <id> remain; the current behaviour launches agents unattended instead.

    4. Correction: there is an internal fence, but it is erased by the same rewrite

    @LLLLLayer — re "no internal escape hatch either": there is one. resumeSleepingAgentSessions skips any record carrying automaticResumeBlockedBy unconditionally, before the validity check.

    It is not usable as a workaround, and the repo already says why, in live-resume-anchor-record.ts:

    since sleepingRecordFromEntry does not copy the flag — erase the fence and later auto-resume prohibited work

    The fence is dropped by the same live-recovery rewrite that produces these records. Raising it because it confirms the conclusion (no user-reachable switch), and because a fence that an ordinary status update silently erases looks like a defect in its own right.


    Happy to open a focused PR for the age bound in isInvalidWorktreeActivationRecord with coverage alongside the existing resume-sleeping-agent-session*.test.ts files, if a maintainer confirms that shape is wanted and what window is acceptable. Not opening one unprompted given #14534 is still awaiting review.

  7. guo commented on Aug 28, 2026

    @guo

    A timestamped capture of this on 1.4.188 (arm64), local macOS, hibernation off — with a repro that needs no app restart, which I think widens the scope of #14534.

    The original report resumes after ⌘Q + relaunch. Here the only actions were: close a tab, then activate that worktree. I instrumented for it because the evidence self-erases — the resurrected pane re-registers the session under its own paneKey, so the profile reads as self-consistent minutes later. A watcher snapshotted orca-data.json + terminal list the instant a new claude --resume process appeared.

    (Shapes, counts and timestamps only — no paths, project names, tab titles, prompts, or session ids.)

    What the user did

    Finished with an agent, closed its tab. The workspace still listed the agent in the sidebar afterwards. Clicking that workspace opened a tab resuming the closed session.

    What was captured

    Two snapshots, five minutes apart, around one session id:

    T+0 T+5min
    live panes in that worktree 14 14
    the user's pane present, its tab launchAgent: null gone
    a replacement pane — present
    tabs in that worktree carrying launchAgent 0 1
    sleepingAgentSessionsByPaneKey entry for the session present, state: "working", origin: "live" absent — consumed by the launch

    Tab-level diff over the same window: one tab id removed (launchAgent: null, the user's own), one tab id added (launchAgent: "claude"). Tab count is unchanged, so a repro that only counts tabs will miss this.

    Process table over the same window, distinguished by argument order — Orca prepends the yolo default from settings.agentDefaultArgs and appends --resume, whereas a hand-started agent has the flags the other way round:

    T+0        claude --resume <id> --dangerously-skip-permissions      <- started by the user
    T+5min     claude --dangerously-skip-permissions --resume <id>      <- started by Orca
    

    So the record outlived the deliberate close, and the next activation appended a new agent tab for it. This is the appends exactly one replacement tab for a husk pane path rather than the cold-restore-in-place variant, and it fires on plain tab-close + activate.

    Why the close does not retire the record

    retireAgentPaneAuthority does remove the record unless called with preserveSleepingAgentSession, and the three call sites that pass it are legacy-worker rollback and retireSurface, not an ordinary close. Yet the record was still present at launch time, five minutes after the close. That points at the record being re-created after the close by the live-recovery writer — consistent with @LLLLLayer's finding that a done status for a resumable agent is rewritten back to state: "working". I could not observe the exact moment of re-creation, so I am flagging that as the likely path rather than asserting it.

    Wider install picture

    19–21 concurrent claude --resume processes, 16 of them carrying Orca's argument order. Two session ids each had two concurrent Orca-started processes, up 2+ days. Four panes were sitting on Claude's Resume from summary / Resume full session as-is picker doing nothing — three of them in tabs with launchAgent: null, i.e. the in-place variant, one in an Orca-created tab.

    Interaction with a second defect

    The replacement tab runs with cwd = the worktree root, but this session's transcript belongs to a different directory than that worktree, so the resumed process cannot find the session and lands on the picker. Filed separately as #16960 — the resume record binds a session to the pane's worktree rather than the agent's cwd. Where both apply, the resurrected pane is not merely unwanted, it is inert: it occupies a pane, shows as an agent in the sidebar, and can never reconcile with the session it names.

  8. max-winderbaum commented on Sep 2, 2026

    @max-winderbaum

    Still reproducing on 1.4.184 (Linux x64), local profile, experimentalAgentHibernation: false, no SSH/remote runtime. Adding a variant that I think is the simplest and most damaging form of this bug: the original process never exits. An idle Claude Code session that is still alive in its pane gets a second claude --resume <id> launched beside it on an ordinary worktree switch. No app restart, no tab close, no sidebar click.

    What happened

    One long-running Claude Code session (a coordinator agent that stays open all day) in a single worktree. Over four hours Orca launched three duplicates of it:

    daemon.log session-created (UTC) pane outcome
    14:15:13 …The-Merging@@9ba7c412 killed by the renderer (clientId 7480cc1c) at 14:16:04
    15:10:50 …The-Merging@@3d4d2225 killed by the renderer at 15:11:15
    15:52:40 …The-Merging@@2c70b234 survived, because I typed into it

    Each landed in a fresh "Terminal N" tab whose scrollback begins with Orca's own quoting:

    claude '--dangerously-skip-permissions' '--resume' '76961160-75e8-48d8-a525-fb4ec657ba63'
    

    Ancestry: orca-ide daemon-entry.js → bash --rcfile ~/.config/orca/shell-ready/bash/rcfile → claude --resume. The original process (pid 1466611, started 11:26 UTC, same session id) was alive and idle the whole time. Both processes then append to the same .jsonl; the transcript now has 450 parentUuid branch points.

    Each launch coincided with me switching worktrees in the sidebar and coming back. The renderer killed the first two within 25–50 s once the original pane reattached, which is why the evidence self-erases (same observation as @guo's 2026-08-28 comment).

    Why the guard passes

    Read from the packaged bundle (1.4.184, store-*.js and worktree-activation-*.js):

    1. Every live Claude session gets a resume record, even while it is running. The agent-status reducer builds liveRecoveryRecord via sleepingRecordFromEntry(..., origin: "live") on every hook update, and when the agent reports done it keeps the record with state rewritten to "working" and prompt: "" as long as getAgentResumeArgv returns non-null (retainsResumableRecoveryIdentity). So an idle-but-alive session always has a standing record. Hibernation is not involved.

    2. activateAndRevealWorktree calls resumeSleepingAgentSessionsForWorktree(worktreeId) unconditionally, on every sidebar click / Cmd+J selection / return from another worktree.

    3. The only liveness check is renderer view state. recordPaneIsOwnedByPreservedPane decides ownership from terminalLayoutsByTabId, ptyIdsByTabId, and tab.ptyId. It never asks the daemon whether the PTY is alive, and it never consults isProviderAgentSessionOwnerLive, which already exists in main. With terminalHiddenViewParking: true (the default), leaving the worktree parks the pane after 30 s; once the layout/PTY maps are dropped the record looks orphaned and launchSleepingAgentSession runs.

    4. activeOrQueuedResumeClaimsProviderSession cannot save it for the same reason Cold restore forks a second local claude --resume across worktrees onto a live transcript #17807 describes: it only sees agentStatusByPaneKey entries for tabs in state.tabsByWorktree[record.worktreeId], and the parked pane's status entry is exactly what was evicted.

    Why this is worse than a stray tab

    Claude Code restores session-only scheduled jobs (CronCreate) when it resumes a transcript. Each duplicate therefore fired the original session's hourly job within one second of launch, with full tool access, while the original was still running the same job. Two copies of an agent with write access to the same repo, beads, and staging environment. Fork #3 was caught only because it had been told to check for peers before acting.

    Fix shape

    #14534 (retire on confirmed exit) and #16845 (age bound) do not cover this: the original never exited and the record is fresh. The wake sweep needs a real liveness check before launching: if any PTY on this machine currently runs a process for that providerSession, focus it or skip, never launch. The daemon already answers listSessions with isAlive, and main already has isProviderAgentSessionOwnerLive; the renderer sweep just does not call either.

    Workaround for anyone else hitting this: run the long-lived agent in tmux instead of an Orca tab, or launch it with env -u ORCA_PANE_KEY claude … so claude-hook.sh exits early and Orca never learns the session id.

  9. daniel-rudaev commented on Sep 8, 2026

    @daniel-rudaev
    Contributor

    On 1.4.197, with a paired desktop + a remote runtime (a Linux box) running the agent, activating a worktree that already holds a live claude session re-launches claude --resume <same session id> as a second tab in the same worktree. Over 35 hours across 26 worktrees we logged 136 occurrences, and it recurred after we switched Agent hibernation off, so this isn't gated by that setting.

    Evidence

    Daemon log shows a session-created entry followed by a startup-command-delivery entry with viaShellArgs: false and queuedByShellReadyBarrier: true. commandLength is either 56 (claude '--resume' '<uuid>') or 83 (same plus '--permission-mode' 'auto'), matching the composed launch command exactly. The desktop then closes the twin itself (session-tabs.close, reason: user). We confirmed directly, via the local agent-status file, one Claude session id present on two panes at once — the original and the duplicate.

    Mechanism (read from the shipped renderer bundle)

    The desktop keeps a live "recovery record" for every running agent, refreshed periodically and independent of the hibernation setting. On worktree activation, it resumes any such record unless the pane can be verified alive. For a remote-runtime tab, that liveness check depends on a local map from tab id to pty id, which is populated only from a session-tabs snapshot's mirrored pty ids — and is deleted whenever that list comes back empty. When the map is empty for a live remote tab, the record reads as unverifiable and the desktop issues a fresh createTab with the resume command. The runtime's terminal-creation entry point (createMobileSessionTerminal) accepts that command verbatim, and because the daemon keys sessions on a tab-suffixed id, it has no way to recognize this as a duplicate of an already-attached session and simply creates a second one.

    Suggested fix

    Key the resume claim on (worktree, agent, provider session id) at the runtime layer, and attach to the existing terminal instead of creating a new one whenever a live terminal already holds that session. Separately, treat an unhydrated/empty pty-id mirror as "unverifiable," not "dead" — it should block a resume rather than trigger one.

    Related

    We're running several shipped fixes in this area already (#11259, #11448, #12574, #13326, #16308, all present in 1.4.197) and the bug still reproduces, so whatever guard those added is incomplete for this remote-runtime path. We also noticed a change to the automatic-resume path on main today (#19544) that may bear on this — happy to test against the first release that includes it, or to share more detail (logs, timestamps, reproduction steps) if useful.

  10. keefar commented on Sep 9, 2026

    @keefar

    This still happens on Mac OS. So annoying, and always resuming finished 65% ctx fable session is expensive.

  11. added theissue type on Sep 19, 2026
  12. wowpotato commented on Sep 21, 2026

    @wowpotato
    Contributor

    Opened #21317 for one specific path in this, and wanted to lay out how it sits against the other work here so nobody duplicates it.

    What it fixes. When the agent exits and the pane returns to its shell, the pane's sleeping-session record is now marked agentExited. Cold restore and the activation sweep both refuse to relaunch from a marked record, so reopening the workspace leaves you at a terminal instead of claude --resume <old id>. The record is flagged rather than deleted: the evidence is a foreground-process read, which can name a shell while the agent is merely suspended (Ctrl-Z), so a misread costs one automatic resume instead of the ability to recover the session at all.

    How it relates to the other PRs here — they are orthogonal, not competing:

    Meanwhile, if the accumulation is bothering you, the records are plain JSON and safe to prune by hand. Quit Orca completely first — it rewrites this file on exit — then in ~/Library/Application Support/orca/profiles/local-default/orca-data.json, drop the state: "done" entries under workspaceSession.sleepingAgentSessionsByPaneKey (keep working ones, and back the file up first). That stops the resurrections until a real fix lands; it does not prevent new records from accumulating.

  13. nwparker commented on Oct 24, 2026

    @nwparker
    Contributor

    Thanks for the report! The clean-exit autoresume path has had fixes (#21714, v1.4.206, clears stale agent identity after shell exit). Please retest on 1.4.239+: exit Claude, exit the shell, quit and reopen Orca. If it still resumes, share: Orca version, OS, Claude Code version, whether you use a remote/paired client or only local, and the entry for that pane from orca terminal list --json before quitting (redacted). Please redact anything private before sharing; screenshots or log excerpts are fine.

  14. syunki4649 commented on Oct 26, 2026

    @syunki4649

    Retested on Orca 1.4.241 (Windows 10 desktop app, SSH host = WSL2 Debian; Claude Code 2.1.310). Still reproduces for records left over from before the update.

    Setup before the update: 1.4.222, a long-lived detached relay on the SSH host held ~19 PTYs. Several tabs in one workspace pointed at PTYs from relays that had already died earlier.

    What happened after updating to 1.4.241:

    1. Opening a workspace whose tabs were still backed by the old relay reattached fine — no resume.
    2. Opening a workspace with stale tabs showed "This terminal is still running on the host under an older version of Orca. Reconnecting to it…" and never recovered.
    3. Running orca terminal close --terminal <handle> on each stale tab did not remove the tabs. Instead, new PTYs were spawned under the new managed server and six previously-exited Claude sessions were started with claude --resume <id> in that workspace.
    4. After stopping all old-relay processes and doing Disconnect → Connect on the SSH host, one more session that had been cleanly exited (/exit) was auto-resumed the same way.
    5. Later, just activating another workspace in the UI resumed one older Claude session with claude --resume <id> (the "activate the workspace" variant described above).

    All of the resumed sessions had sleepingAgentSessionsByPaneKey records captured at the moment of the update. #21714 keeps sleeping identity for remote panes, so my guess is that records written while these were SSH-relay panes were never retired.

    What looks fixed: on a fresh worktree on the new managed server, I started Claude, exchanged one message, and ran /exit. Afterwards there was no sleepingAgentSessionsByPaneKey record for that pane in profile-state.db. orca terminal close --worktree <sel> --all then closed both terminals and the tabs without triggering any resume.

    Remaining gaps:

    Possibly separate:

    • From a shell outside Orca on the SSH host, orca terminal create fails with No PTY provider for connection "<ssh-id>": the SSH relay for this host is not attached while the host shows as connected (also after Disconnect/Connect). Adding --environment <saved server> works, and the tab appears in the UI.
    • Before the old relay was stopped, orca terminal close --worktree <sel> --all reported "At least one PTY was not confirmed stopped: no registered provider can observe its host" and only stopped PTYs on the old relay.
    • Closing the resumed tabs with the tab "×" in the UI did stop the resumed sessions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingos:macosIssues affecting macOSssh

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions