Repository navigation
Merge abate's approved PRs #538 #539 #483 #479 #477 - #548
Conversation
… 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.
…listing it apart)
|
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 |
|
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 Fix in progress on
@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. |
…, 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.
…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.
…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 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 |
…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.
…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.
…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.
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):
public/index.htmlkeeps main's header layout from (file-panel): move the session tools to a right-hand bar (#506) #511, with (sandbox): opt-in writable skills and agents #483's new sandbox tooltip text.persistWorkingSetcombines (restore): save the open sessions when the app closes #479's held-active entry with (restore): a backgrounded session is restored from its pre-fork transcript #518'skeepAttached.flushStateForExitsits next to main'srunRestore(list, { retryAfterIndexing }). The tests take the union of theloadAppFunctionsdeclarations.cli-session-state.jskeeps (bg-agents): on Windows, Switchboard's own tabs show as External and count as live elsewhere #521'sownPidTest/ownProcessFilterand addsclearOwner, adapted tochainReachesandstartProcessTableRefresh.public/app.jsmerges (sessions): follow /clear to the new transcript instead of listing it apart #477'sfreshandclearedentries with (restore): a backgrounded session is restored from its pre-fork transcript #518's continuations and (restore): save the open sessions when the app closes #479's exit flush.terminal-webgl-lifecycleandtool-bartests now slice the renamedapplySessionForked.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.