Skip to content

fix(mothership): keep local file tools running when the chat view changes - #8666

Merged
waleedlatif1 merged 5 commits into
stagingfrom
fix/desktop-tool-followups
Oct 6, 2026
Merged

waleedlatif1 merged 5 commits into
stagingfrom
fix/desktop-tool-followups

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Follow-ups to #8652, all serving "work keeps running when I leave a chat":

  • Local file tools keep running, and each turn's Stop reaches its own desktop tools.
    • Browser actions, read_local_file, imports and user-local VFS reads share one lifetime, owned by their turn (keyed by the turn's stream id in home/hooks/desktop-tool-lifetimes.ts), not by a stream reader or a view. Only that turn's Stop ends it.
    • Each running tool holds a lease on its turn and releases it when it settles. A turn is kept only while one of its tools runs, so a live turn can't be evicted and settled turns leave nothing behind.
    • A history reconnect, a stream recovery or leaving the chat view never aborts a running tool: it finishes and reports.
    • Stop in another chat leaves it running. Stop from a view reopened on the same turn still reaches it.
  • One classifier. isDesktopExecutedToolCall (client-executed-tools.ts) duplicated isDesktopToolCall. It's removed, and the stream tool handler and isClientExecutedToolCall use the single classifier in lib/mothership/tools/desktop-tools.ts.
  • A report the server refuses as final is final.
    • When the desktop app holds a call (claimed, or a report raced its claim and lost, or a not-started report arrives for a claimed call), /api/copilot/confirm now answers 409 instead of 404.
    • The completion reporter treats 409 as final, including on the trimmed retry of an oversized report, instead of retrying five times with backoff and logging an error.
    • 404s are unchanged, because some workflow 404s are transient and still worth retrying.
  • Model-actionable not-started wording. The results say what is known: nothing in the Sim desktop app picked the action up and nothing happened; don't retry it in this turn; ask the user to keep this chat open in the Sim desktop app or ask again later. For a local read whose result was lost, the message follows the stored claim: one the desktop claimed is described as started; one with no claim (an older desktop reads without claiming) as possibly never started, and reading changed nothing. The result shapes (notStarted, outcomeUnknown + doNotRetry) are unchanged.
  • Obsolete probe deleted. scripts/probes/mothership-request-admission.ts called removed repository APIs (stopPendingRequest, owner-token-less claims, old signatures) and built its schema from the 0317 snapshot. Nothing ran or referenced it except an explicit-any baseline entry, which shrinks. The same admission behaviour is covered against real Postgres by orphaned-runs.integration.ts, stream-recovery.integration.ts and desktop-tool-authorization.integration.ts.

Not included: persisting desktopClaimsLocalReads in the recovery config. StreamRecoveryConfigSchema is .strict(), so during a rolling deploy an older pod would reject a recovery config carrying the new field and fail to recover the turn. Doing it safely takes two deploys: readers accept the optional field first, writers send it later.

Type of Change

  • Bug fix

Testing

  • use-chat.dom.test.tsx: the in-flight desktop action suite now runs for both a browser action and a read_local_file. Recovery and unmount leave it running; Stop cancels it, including after a recovery and from a view reopened on the turn; Stop in another chat leaves it running. Before this change the local-file recovery and unmount cases fail; the post-recovery, reopened-view and other-chat cases fail for both kinds on the intermediate view-owned design.
  • desktop-tool-lifetimes.test.ts: Stop cancels every running tool of its turn and no other turn; a turn with a running tool stays reachable however many other turns come and go (fails under a bounded LRU); a settled turn's next tool gets a fresh lifetime; a tool settling after Stop can't release a newer lease.
  • completion.test.ts: a 409 settles the report at once, including on the oversized-payload retry, with no retry backoff. Both fail before.
  • confirm/route.test.ts: the held-call refusals answer 409. desktop-tool-pickup.integration.ts asserts the stale not-started report gets 409 against real Postgres.
  • executor.test.ts: a claimed local read whose result was lost is called started; an unclaimed one isn't. Both fail without their guard.
  • Reverted each guard on its own and watched its test go red.
  • bun run lint, bun run type-check, bun run check:audits, bun run test, bun run test:integration, bun run docs-manifest:check, block-registry check: all pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…nges

Local file reads and imports now take the same Stop-only lifetime as browser
actions: a history reconnect, a stream recovery or leaving the chat view
leaves them running so they finish and report, and only the user's Stop
cancels them.

The desktop-tool classifier in client-executed-tools.ts is removed in favour
of the single one in tools/desktop-tools.ts.

When the desktop app holds a call, confirm now answers 409 to any other
reporter (a stale replay, a lost race against the claim), and the client
treats 409 as final instead of retrying a 404 five times.

The not-started results now tell the model what it can act on: the action
never started; don't retry it in this turn; ask the user to keep the chat
open in the Sim desktop app. A local read the server cannot see picked up
is no longer described as started.

The obsolete admission probe script is deleted. Nothing ran it, it no longer
type-checked against the repository API, and the integration suites cover
the same admission behaviour against real Postgres.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner October 6, 2026 05:01
@vercel

vercel Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Oct 6, 2026 6:35am UTC

Request Review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 19 files

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/mothership/tools/client/completion.ts Outdated
Comment thread apps/sim/lib/mothership/request/tools/desktop-wait.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 19 files

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts Outdated
@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Refactors how desktop tools persist across chat view changes.

The PR appears safe to merge; no outstanding finding or new issue remains.

Summary

The PR moves desktop-tool cancellation to turn-scoped leases, lets local file and browser work survive chat-view changes, and treats server refusals for claimed calls as final. Since the previous review, the test import was changed to the repository’s absolute-path convention.

Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Turn[Chat turn and stream ID] --> Lease[Desktop-tool lease]
  Lease --> Tool[Browser or local-file tool]
  View[Chat view or stream reader] -->|May change without aborting| Tool
  Stop[Stop for this turn] -->|Aborts| Lease
  Tool -->|Settles and releases| Lease
Loading

Reviews (5) · Last reviewed commit: "test(mothership): import the lease regis..."

Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts Outdated
Comment thread apps/sim/lib/mothership/request/tools/executor.ts Outdated
…ults say what is known

Desktop tools now take one lifetime the view owns and only the user's Stop
ends, so Stop still reaches a tool that a replaced stream reader started. A
held-call 409 is final on the trimmed retry of an oversized report too. The
not-started result no longer asserts why nothing picked the call up, and a
local read the desktop claimed is described as started when its result is
lost.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 19 files

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/mothership/request/tools/executor.ts
Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts Outdated
Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts Outdated
A desktop tool outlives the chat view that started it, so its Stop belongs
to the turn, not the view: tools are keyed by the turn's stream id, which
survives reader replacement and remounts and differs between chats. Stop in
another chat leaves them running, and Stop from a view reopened on the turn
still reaches them.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 20 files

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/desktop-tool-lifetimes.ts Outdated
Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/desktop-tool-lifetimes.ts Outdated
Replace the bounded LRU of turn controllers with leases: each running
browser action or local file tool holds its turn's Stop until it settles,
so a live turn can never be evicted and settled turns leave nothing behind.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 23 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/desktop-tool-lifetimes.test.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 23 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@waleedlatif1
waleedlatif1 merged commit 65b247f into staging Oct 6, 2026
41 checks passed
waleedlatif1 added a commit that referenced this pull request Oct 6, 2026
… folders, and bound the outbox in bytes

- Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`.
  A Stop that lands while the session resolves means none of them starts.
  A batch of keys or lines stops between keystrokes and says some input may
  already have arrived.
- A terminal call stopped while it waited for the chat's previous operation
  never reaches the terminal.
- Sign-out stops running actions before it waits on a reconcile in flight.
- The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII
  results still reads back after a restart.
- A local read, grep, glob, list or stat answers only while its folder is
  still granted when it finishes. This covers the chat view's path as well.
- Grep says when its results are truncated, as glob does.
- A doorbell stream that keeps closing as soon as it opens backs off instead
  of reconnecting every second.
- The not-started results follow #8666's wording: what happened, that nothing
  ran, and what the model should do instead of retrying.
waleedlatif1 added a commit that referenced this pull request Oct 6, 2026
… folders, and bound the outbox in bytes

- Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`.
  A Stop that lands while the session resolves means none of them starts.
  A batch of keys or lines stops between keystrokes and says some input may
  already have arrived.
- A terminal call stopped while it waited for the chat's previous operation
  never reaches the terminal.
- Sign-out stops running actions before it waits on a reconcile in flight.
- The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII
  results still reads back after a restart.
- A local read, grep, glob, list or stat answers only while its folder is
  still granted when it finishes. This covers the chat view's path as well.
- Grep says when its results are truncated, as glob does.
- A doorbell stream that keeps closing as soon as it opens backs off instead
  of reconnecting every second.
- The not-started results follow #8666's wording: what happened, that nothing
  ran, and what the model should do instead of retrying.
@waleedlatif1
waleedlatif1 deleted the fix/desktop-tool-followups branch October 6, 2026 16:48
waleedlatif1 added a commit that referenced this pull request Oct 6, 2026
… folders, and bound the outbox in bytes

- Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`.
  A Stop that lands while the session resolves means none of them starts.
  A batch of keys or lines stops between keystrokes and says some input may
  already have arrived.
- A terminal call stopped while it waited for the chat's previous operation
  never reaches the terminal.
- Sign-out stops running actions before it waits on a reconcile in flight.
- The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII
  results still reads back after a restart.
- A local read, grep, glob, list or stat answers only while its folder is
  still granted when it finishes. This covers the chat view's path as well.
- Grep says when its results are truncated, as glob does.
- A doorbell stream that keeps closing as soon as it opens backs off instead
  of reconnecting every second.
- The not-started results follow #8666's wording: what happened, that nothing
  ran, and what the model should do instead of retrying.
waleedlatif1 added a commit that referenced this pull request Oct 6, 2026
… folders, and bound the outbox in bytes

- Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`.
  A Stop that lands while the session resolves means none of them starts.
  A batch of keys or lines stops between keystrokes and says some input may
  already have arrived.
- A terminal call stopped while it waited for the chat's previous operation
  never reaches the terminal.
- Sign-out stops running actions before it waits on a reconcile in flight.
- The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII
  results still reads back after a restart.
- A local read, grep, glob, list or stat answers only while its folder is
  still granted when it finishes. This covers the chat view's path as well.
- Grep says when its results are truncated, as glob does.
- A doorbell stream that keeps closing as soon as it opens backs off instead
  of reconnecting every second.
- The not-started results follow #8666's wording: what happened, that nothing
  ran, and what the model should do instead of retrying.
waleedlatif1 added a commit that referenced this pull request Oct 6, 2026
…8668)

* feat(desktop): run a chat's desktop tools in the background executor

The Sim desktop app now picks up and runs the desktop tool calls of turns bound
to it from Electron main, so work keeps going when the user leaves the chat,
switches workspace, or reloads the window. It speaks the device protocol from
the previous PRs and stays dormant until Sim enables it for the user.

- Identity: a stable install id in userData, registered with the app's own
  session on sign-in and session change, retired on sign-out (which also stops
  every action and clears the outbox). A 409 mints a new id.
- Pickup: an SSE doorbell plus the reconciling inbox read on connect, on every
  ring, on wake and network return, and on Sim's reconcile timer.
- Execution: each call is claimed as soon as it is offered, queued per chat and
  surface, and run in that chat's own browser scope, terminals, or granted
  folders, from Sim's record of the call. Leases are renewed from claim to
  acknowledgement. Covers browser_*, terminal, read_local_file and user-local
  read/grep/glob; import_local_files is answered as not run for now.
- Outbox: an encrypted journal (claiming, claimed, started, result) written
  before each step. A restart reports a call that never started as not
  started, one that did as outcome unknown, and a finished one with its result;
  nothing runs twice. Results are retried with backoff until Sim answers.
- Stop: inbox cancels and refused lease renewals stop the action. Terminal runs
  get a real cancel: Ctrl-C, then SIGTERM and SIGKILL to the foreground process
  group.
- Approvals: a notification for a background chat waiting on the user, opening
  the chat at its approval card and closing once decided; it never names the
  command.
- preload exposes desktopExecutor.getDevice() so the composer binds turns.

The browser, terminal and local-filesystem result projections move from the web
app into @sim/desktop-bridge so the chat view and the executor build identical
results; the renderer behaviour is unchanged.

* fix(desktop): make the executor's journal, sign-out and terminal stops hold under races

- A journal transition that cannot be made durable now fails: an unrecorded
  claim is not made, and an action whose start could not be recorded is not
  run and is reported as not run. The journal keeps result data within what a
  restart can read back.
- Sign-out fences registrations in flight, and a rotated install id never
  reuses the previous id's client.
- A stopped call's result carries no content, only the acknowledgement.
- Terminal stops: a Stop before the command starts runs nothing; escalation
  signals only the stopped command's own process group, rechecked before each
  signal; a stopped tmux run ends its wait at once.
- A chat's next terminal operation waits for one that outlived its deadline.
- The doorbell bounds its handshake and reconnects at once on wake.
- Approval notifications name neither the chat nor the command.
- A user-local glob that stopped early says so.

* fix(desktop): stop every terminal operation, answer only from granted folders, and bound the outbox in bytes

- Stop reaches `input`, `kill` and pane `close`, not just `run` and `handoff`.
  A Stop that lands while the session resolves means none of them starts.
  A batch of keys or lines stops between keystrokes and says some input may
  already have arrived.
- A terminal call stopped while it waited for the chat's previous operation
  never reaches the terminal.
- Sign-out stops running actions before it waits on a reconcile in flight.
- The outbox budgets result data in UTF-8 bytes, so a journal of non-ASCII
  results still reads back after a restart.
- A local read, grep, glob, list or stat answers only while its folder is
  still granted when it finishes. This covers the chat view's path as well.
- Grep says when its results are truncated, as glob does.
- A doorbell stream that keeps closing as soon as it opens backs off instead
  of reconnecting every second.
- The not-started results follow #8666's wording: what happened, that nothing
  ran, and what the model should do instead of retrying.

* test(mothership): a desktop tool's executor resolves only once the tool has settled

* fix(desktop): hold refused results until the device registers again, and stay awake through recovery

- A result Sim refuses because it no longer recognizes the device is parked
  rather than retried every 30 s. The journal keeps it, and it is sent once
  the device registers again. The service is told once.
- A dormant device's 15-minute registration recheck is no longer pushed out
  by every refused request.
- A 408 is retried like any other timeout.
- Results a previous app run left behind keep the executor busy until Sim
  has them, so the machine does not sleep with them undelivered.
- A Sim that answers registration with another protocol version gets no new
  turns bound to this device.
- Selecting an already granted folder again while a read runs keeps the
  read's answer. Only a revoked or different grant discards it.

* fix(desktop): stop the agent's terminal commands at sign-out, by their own process groups

Commands the agent started could outlive the session that started them.
Closing a shell only hangs up its foreground, so a command that ignores
SIGHUP kept running, and a tmux run outlives the Sim terminal entirely.

- Sign-out and an account change now stop every command the agent started
  before the shells are torn down. A plain shell's command is stopped by its
  own process group, the same Ctrl-C, SIGTERM, SIGKILL escalation Stop uses,
  and each tmux run window still going gets Ctrl-C, then is closed.
- A command the user started is never touched: only a command a `run`
  started counts as the agent's.
- The agent's command stays the agent's until it really exits, even after an
  interactive command detaches from its tool call. Stop and sign-out can
  still end it then.

* fix(desktop): a signed-out executor goes idle once and never speaks for its successor

dispose() now reports idle once. A recovered delivery that settles after
sign-out no longer reports through the shared busy callback, where it
could release the sleep blocker the next session's executor was holding.

* fix(desktop): stop agent tmux runs from the moment they start, and when Terminal is switched off

- A tmux run is tracked as soon as its window exists, not once its wait
  ends. Sign-out now reaches a command the chat view started that is still
  inside its wait window. Reaping skips runs whose call is still reading
  their files.
- Switching Terminal off stops the agent's commands before the shells go,
  as sign-out does.
- The runner's deadline is built on the shared interruptible sleep.
- A hostname longer than Sim's 128-character limit is shortened, so
  registration does not fail on it.

* fix(desktop): keep sign-out, Terminal off and run files from racing each other

- Sign-out waits for a restart's journal walk before clearing the journal,
  and a walk or delivery that starts after sign-out sends nothing, so none
  of the previous session's results outlive it.
- An unrecognized device's stopped loops stay stopped: the reconcile timer
  no longer re-arms itself after them.
- Switching Terminal off disposes the shells only if it is still off once
  the agent's commands have stopped.
- A tmux run whose terminal closes mid-wait keeps its output files until its
  call has read them.
- A terminal operation that outlives its deadline is reported as outcome
  unknown and not to be retried, as a browser action is.

* fix(desktop): say why local files are out of reach, and drop non-null assertions in tests

When the app has no usable account storage (signing out, switching
account, storage unavailable), a local file call now says so. It no longer
tells the model a setting is off and to ask the user to switch it on.

* test(desktop): drive the doorbell backoff test with fake timers

* fix(desktop): stop only the run's own tmux pane, stay dormant while the executor is off

- A tmux run's pane is tagged with a per-run @sim-run-id option. Stops and
  closes check that tag first and act on the run's own pane only, never the
  active pane or the whole window. A pane the user split beside it, or an id
  a restarted tmux server handed to one of the user's panes, is never
  touched. A run whose pane is gone is released.
- With the executor off for this user, the app no longer opens the inbox or
  the doorbell. It rechecks registration on the existing triggers and every
  15 minutes. Against a Sim without the executor routes (404 or 405) it
  stays dormant until the next launch, session change or toggle.
- An inbox read that Sim answers after sign-out raises no approval
  notification and claims nothing.
- A result this device cannot encode is reported once as outcome unknown,
  instead of being retried forever as if the network had failed.
- Only a missing install-id file counts as no id; any other read failure is
  retried rather than minting a new id over the old one.
- A generic terminal failure reports its code to the model again.
- The doorbell, notifier and service tests observe state through typed
  fakes.

* fix(desktop): a result that keeps failing to reach Sim stops keeping the machine awake

Delivery still retries it, but after ten minutes of failures it no longer
counts as work the machine must stay awake for.

* fix(desktop): refuse untaggable tmux runs, keep install ids durable, never reject a reconcile

- A tmux run whose pane cannot be tagged is closed at once and reported,
  so nothing the agent starts is ever beyond a later stop.
- A pane tmux cannot be asked about is neither stopped nor given up on;
  only a confirmed absence or a mismatched tag releases a run.
- A new install id is used only once it is saved. Registration retries
  instead of running under an id the next launch would not find.
- A Sim without the executor routes is rechecked every 15 minutes, logged
  once, so an upgrade reaches an open app.
- An inbox read never rejects: a failing notifier is logged, and the
  read settles.
- A long completion message is shortened without splitting a character.

* fix(desktop): a tmux run starts only once its pane is tagged, and sign-out reaches runs of closed tabs

- A run's command now waits for its pane to be tagged as the run's before
  it starts. Untagged, it never starts and its pane closes by itself, so no
  pane is ever closed by an id a restarted tmux server might have handed to
  the user. A short command can no longer finish before the tag exists.
- The run script is a file instead of a bash -c string. tmux hands its
  command to sh -c, which expanded the counter and PIPESTATUS meant for
  bash first, so tmux runs reported no exit code.
- A run whose Sim terminal closed while it kept going in tmux is still
  stopped at sign-out and when Terminal is switched off.
- Retiring an install id either replaces it or removes it from disk, so a
  retired id is never picked up again. A 409 whose new id cannot be saved
  backs off instead of retrying at once.
- A failing approval notification no longer keeps the inbox read from
  claiming its calls.

* test(desktop): a throwing notifier still claims, and sign-out reaches a closed tab's tmux run

* fix(desktop): gate a run past tagging's timeout, forget gone orphans, re-register when back online

* test(desktop): drop a test that only checked which tmux calls were made

This branch was previously deployed

1 inactive deployment
Preview — b609f821 Deployed Oct 6, 2026 by vercel[bot]
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.

1 participant