Skip to content

Merge abate's approved PRs #538 #539 #483 #479 #477 - #548

Merged
devsuitup merged 22 commits into
mainfrom
integ/abate-1011
Oct 11, 2026
Merged

devsuitup merged 22 commits into
mainfrom
integ/abate-1011

Conversation

@devsuitup

Copy link
Copy Markdown
Owner

Lands @abate's five approved PRs: #538, #539, #483, #479, #477. Each was reviewed and approved on its own head. All five conflicted with main after v0.0.92, #532 and #542, so they are merged here one by one with --no-ff, which keeps abate's commits and authorship. Merge this PR with a merge commit, not a squash, so that GitHub marks the five PRs as merged.

Conflict resolutions (the only code in this PR that is not abate's):

Local: lint 0 errors. Targeted tests: 433 pass, 0 fail. Full suite: 3 failures, in the files later fixed, which now pass 72/72. The full suite has not been re-run since.

abate and others added 22 commits October 9, 2026 15:47
… it apart

/clear makes the CLI open a new jsonl under a new session id that names
nothing of the one it replaced, so fork detection never matched it: the
terminal stayed on the old row and the new conversation appeared as a
separate, unattached session. The CLI's state file switches to the new id
on /clear; resolve its pid up to the PTY's and re-key the session like a fork.
…ins on it

An owner that cannot be established (no /proc: macOS, Windows) was matched
when one Claude PTY ran in the folder, so a claude run outside the app could
be taken over; it is now never matched. A re-key kept no trace of the old id,
so a trigger chain that sent /clear lost its terminal: the trigger context
now resolves ids through the re-keys. A pending owner, or a new file whose
first records are not written yet, is rechecked a second later while fresh,
instead of waiting for another change in the folder. The snapshot-only fork
matcher no longer takes a /clear file. A session with no transcript yet is
left out of the saved set, which is saved again once its first prompt makes
it real.
Skills and agents are read-only in a sandboxed session because an
unsandboxed claude loads them later. Some workflows need the session to
write them, so SWITCHBOARD_SANDBOX_RW_SKILLS=1 and
SWITCHBOARD_SANDBOX_RW_AGENTS=1 (Pre-launch Command) make them session
state in ~/.claude and every bound .claude. Both stay off by default;
docs/sandbox.md states the protection given up.
Review of #483: with SWITCHBOARD_SANDBOX_RW_SKILLS/AGENTS, a skills or
agents directory linked to a repository elsewhere was bound writable
without the protections other writable roots get, so the session could
write that repository's hooks or a settings file linked into it.

- An opted-in link target is a writable root, so files linked into it
  from ~/.claude are mounted read-only.
- The tree under an opted-in entry, real or linked, gets the same .git
  and .claude protection as a bound directory, without creating .claude.
- The Sandbox tooltip names the opt-in among what is not isolated.
- The test rig drops inherited SWITCHBOARD_SANDBOX_* variables, which
  leaked into the wrapper when the suite ran from a sandboxed session.
The working set was written on a 500 ms debounce, so the last change before
a quit could be lost, and the shutdown's own PTY kills arrived as session
exits that could save an empty set. A confirmed close or quit now writes the
set at once before answering main, then stops saving it; main stops sending
process-exited once before-quit kills the sessions.
…d-only path

Linked to the same directory as hooks (or agents, commands), or inside a
read-only target such as hooks, plugins or a repository's hooks directory,
the writable bind of skills or agents overrode the read-only one and left
the protected files writable. The finished mount list is now checked and
such a launch refused, whatever order the entries were bound in.
…rted one

The final write skipped a restore in progress, so a session stopped during
it came back, and wrote [] before the saved set was even restored. It now
writes through pendingRestoreEntries, which keeps the sessions a cold index,
the Restore prompt or the restore still hold, and writes nothing before the
saved set is read. A save asked during the grace period of an exit that did
not happen is written when it ends. A Windows logoff asks the renderer to
save, best effort.
… resolved paths

A protected path was mounted read-only under the first writable root that
contains it only, so a repository reached through a symlinked Additional
Directory and through an opted-in skills target stayed writable at the
second path. The opt-in overlap check compared mount destinations, as
spelled, with resolved paths, so a project reached through a symbolic link
escaped it. The opted-in link is now checked for links the session could
re-point, and the search below a bound directory enters one that is a link.
… active marker

A quit or close that joins an unanswered reload question now tells the
renderer the question is an exit, so the answer still waits for the
final write. A cancelled Windows logoff no longer approves every later
quit. Quitting before the restore ends keeps the saved active session
marked. Tests pin the startup read, the awaited flush, the skip flag on
a direct save and the exit-flush listener.
… lock per terminal

A transcript read before its /clear record stays eligible until its first
turn. A snapshot-only file is taken as an awaited fork only when the CLI
under the PTY owns it. A session with no transcript yet is saved as a
fresh entry and restored as a new session in its project. Trigger locks
are keyed by the PTY, so the old and new id of a re-keyed session share
one queue; every older id now resolves to the newest in one lookup.
… protected

find does not descend into a starting point that is a symbolic link, so a
repository or a .claude below an Additional Directory given as a link is
never found, and its .git/config, hooks and settings stay writable.
…hen protecting below it

find does not descend into a starting point that is a link; a trailing
slash makes it enter the directory the link points to.
Regression test alone, expected to fail: both clicks pass the
openSessions check before the first await, so open-terminal is
requested twice.
openSession now keeps each session's open in flight (openingSessions,
id -> promise) and returns the same promise to a duplicate call, so a
double click requests one terminal and callers reading the result see
the real outcome. The body moves to openSessionNow.
…de a real sandbox

Also name in the changelog the refusal a linked Additional Directory now
shares with a plain one.
… opens of one continuation

An automatic open of a session live elsewhere refuses without asking; a click
joining it must still ask. A click resolving to a continuation the restore is
opening must not start a second terminal. Also pin that a rejected or failed
open lets the session be opened again.
…ontinuation id

A click finding an automatic open of the same session in flight waits for it
and then runs as a click, so a session live elsewhere still asks. An open
whose continuation resolves to another id waits for any open of that id and
registers under it, so a click and the restore cannot both start it.
@devsuitup

Copy link
Copy Markdown
Owner Author

Adversarial review at e1c796e: no blocking finding in the five conflict resolutions.

Checked:

Targeted local run: 352 pass, 0 fail, 57 skipped (Linux-only sandbox tests).

Two narrow integration races appear only once #477, #479 and #518 are together, both when quitting during a restore. There is also a test gap on clearOwner's process-table path. Filed as #550, not blocking.

@devsuitup
devsuitup marked this pull request as ready for review October 11, 2026 12:07
@devsuitup
devsuitup merged commit 8b12045 into main Oct 11, 2026
12 checks passed
@devsuitup
devsuitup deleted the integ/abate-1011 branch October 11, 2026 12:07
@devsuitup

Copy link
Copy Markdown
Owner Author

Correction to this merge: #483, #479 and #477 went in at their 09/10 heads (93672b5, 8bf1c90, 6923cd7), not at the heads that were reviewed and approved (8aa60be, b41237a, a468b5b).

How it happened: my local refs predated the 10/10 force-push, and the unforced git fetch pull/N/head:ref silently kept them. #538 and #539 are correct.

Fix in progress on integ/abate-fix:

  • revert those three merges;
  • merge the reviewed heads;
  • fix the integration tests;
  • then a new PR, a review and CI.

@abate: #540, #478, #441 and #553 are now based on 8b12045, so they sit on the stale versions. Once the fix lands they may need a rebase. I'll say on each PR what it needs.

abate added a commit to abate/switchboard that referenced this pull request Oct 11, 2026
…, a linked dir, or a repository's .claude

Tests for the last review round of devsuitup#483, which devsuitup#548 did not merge: a
repository inside an opted-in directory keeps a read-only .claude, a
read-only directory bound under another spelling is refused, a private
~/.claude entry or ~/.claude.json linked into an opted-in directory is
refused, and the real-sandbox test covers a project .claude/skills opt-in.
abate added a commit to abate/switchboard that referenced this pull request Oct 11, 2026
…d active marker

Tests for the last review round of devsuitup#479, which devsuitup#548 did not merge: a quit
that joins a reload check the page already answered still writes the app
state, and a reload check a quit joined quits without reloading first.
Two more pin existing behaviour: main stops reporting session exits once
the quit starts killing them, and a not-started entry holding the saved
active marker keeps it over a reattached session shown meanwhile.
abate added a commit to abate/switchboard that referenced this pull request Oct 11, 2026
…by id, and owner checks

Tests for the last review round of devsuitup#477, which devsuitup#548 did not merge: the row
after /clear is a new session rather than a copy of the cleared one; /reset,
/new and /clear with a name are followed; a trigger waits for a chain whose
session got a new PTY under the same id; owner logs fire on change only;
clearOwner reads the process table on Windows and never claims on macOS;
held header-only files and fresh entries wait for indexing.
@devsuitup

Copy link
Copy Markdown
Owner Author

@abate I see the tests-only commits on #483 (cdc61d0), #479 (25cc779) and #477 (4568996), rebased on main 8b12045: the delta of the last review round redone red-first on top of what #548 merged.

I'll go with that and drop my own correction path (reverts of the stale merges + merges of 8aa60be/b41237a/a468b5b on a local branch, never pushed), so we don't land the same code twice. I'll review each PR once its fix commit is pushed, comparing the result with the reviewed heads, and merge the approved ones into v0.0.93.

One thing to watch: main now also has #540 and #553, and #540 edits .ai/contexts/viewer-panel.md (the unsaved-guard "Main" and "Bounded" bullets) and the CHANGELOG next to the #479 lines, so #479 will likely need a rebase.

abate added a commit to abate/switchboard that referenced this pull request Oct 11, 2026
…d active marker

Tests for the last review round of devsuitup#479, which devsuitup#548 did not merge: a quit
that joins a reload check the page already answered still writes the app
state, and a reload check a quit joined quits without reloading first.
Two more pin existing behaviour: main stops reporting session exits once
the quit starts killing them, and a not-started entry holding the saved
active marker keeps it over a reattached session shown meanwhile.
abate added a commit to abate/switchboard that referenced this pull request Oct 11, 2026
…by id, and owner checks

Tests for the last review round of devsuitup#477, which devsuitup#548 did not merge: the row
after /clear is a new session rather than a copy of the cleared one; /reset,
/new and /clear with a name are followed; a trigger waits for a chain whose
session got a new PTY under the same id; owner logs fire on change only;
clearOwner reads the process table on Windows and never claims on macOS;
held header-only files and fresh entries wait for indexing.
abate added a commit to abate/switchboard that referenced this pull request Oct 11, 2026
…d active marker

Tests for the last review round of devsuitup#479, which devsuitup#548 did not merge: a quit
that joins a reload check the page already answered still writes the app
state, and a reload check a quit joined quits without reloading first.
Two more pin existing behaviour: main stops reporting session exits once
the quit starts killing them, and a not-started entry holding the saved
active marker keeps it over a reattached session shown meanwhile.
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.

2 participants