Repository navigation
An idle chat on a paired computer hands its hold to the relay, and opening it elsewhere takes it - #2287
Draft
AbirAbbas wants to merge 48 commits into
Draft
An idle chat on a paired computer hands its hold to the relay, and opening it elsewhere takes it#2287AbirAbbas wants to merge 48 commits into
AbirAbbas wants to merge 48 commits into
Conversation
…is on the relay When a chat on a computer that shares its identity with another goes idle, its saved state (turns, settings, workspace, vault) is certified and uploaded outside the admission lock, then the hold is released in a short bounded step. Running, queued, starting or parked work keeps the hold. New work takes the hold back in place, or is refused if another computer took the chat. A home with one device makes no new requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t asking A chat whose hold was released or has lapsed is marked openable in the list, only for paired homes and only from the directory's own clock. Opening it fetches and takes it with a non-forcing conditional acquire, checked again at acquisition, so a holder that renews during the fetch keeps the chat. A live hold still offers Continue here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a free chat directly An ordinary message in the window that released the chat reacquires the hold through the work gate: no restore, no setup question, and local files such as node_modules stay. A Home row for a free chat pulls the newest saved head even when an older viewer of it is open here. Remote pre-send checks honour the caller's deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eleased chat back Rows carry an optional can_open. agents.open with a shared chat's id takes a free chat without the Continue-here step. Sends, queues, steers, retries and actions in a window that released its chat take the hold back first; app queues and actions count as work, so they keep the hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t moves The copy bucket was "copy-" plus the origin's full encoded path, so a chat moving back and forth nested the name and failed with 'file name too long' on the third hop. A chat that already has a local root keeps it, an origin that is itself a copy reuses its own name, and longer names are capped at 240 bytes with a stable hash. Existing short names are unchanged. This predates the idle hand-off, which makes repeated moves common. (The filing code moved to internal/syncsetup/copy_filing.go in the previous commits.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Describe idle release, opening a free chat directly, take-back in the same window, and that a running chat still asks. Add the change entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lock-defer law keeps an absorbed panic from becoming a deadlock. The open-row lookup and the live drive's idle checks now read under a helper that unlocks from a defer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ver undoes a take-back The terminal seals through the warm native daemon, whose capture plan is metadata-only, so the saved-state proof refused it and the hold was renewed forever. Certification now asks for a complete standalone plan; ordinary sealing stays on the daemon. A lost optimistic comparison or a competing local vault round retries at the quiet cadence. Ownership notices carry a version, so a release notice delivered after a newer acquisition is dropped. A held writer whose fresh ownership read fails keeps working offline, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the idle-release vault step uses the non-blocking lock. An ordinary SyncState waits under its own context, as on the base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A nested project under a received copy keeps its own bucket, and the long-name hash is taken from the original path rather than the lossy encoded one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d first Adds the real-terminal test: openV3Agent and runSurface with the default native daemon, an on-disk relay and two homes, asserting release, in-place take-back and that effort and memory set after the last turn reach the other computer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wnership door Effort, model choices, approval posture, places, task effort and a remembered note retake the hold or get the held-elsewhere refusal before they are saved, so a save is never acknowledged where it cannot reach the relay. Unpaired and unreleased windows keep their old path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ars a stale release snapshot A canceled or expired Close budget skipped the base cleanup and left an ordinary writer running; Close now keeps the idle-join error and always runs the capture, stop, cancel and join it ran before. A zero send receipt from the release is discarded once a reacquire has cleared the viewer, so a send after take-back is no longer refused as "available to continue here". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ld first Rename, rewind, task rooms, deletes and merges, plan steering, autonomy, names, anchors, standing tree and remembered notes enter the same released-window door before they write, so none is acknowledged where it cannot reach the relay. Every other window returns to its old path before any network read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Beyond 20,000 paths, 50,000 entries or 64 MiB of chat files the proof fails and the chat keeps its hold, as before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s install lock The drive is published through an atomic pointer, so a window that never released no longer waits on catch-up disk work before its own check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… an unknown outcome is honest The take-back acquire ran under the 750 ms admission read and could time out after the relay had committed it; the window then refused its own send as "available to continue here". It now has a bounded 15 s budget, held admission keeps 750 ms, and an unknown outcome keeps the draft with a retryable line. A hold this computer cannot attribute to a window is never adopted or offered back to itself; it lapses and a retry takes the chat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the idle hand-off up to app/v3 a687c92, resolving 13 files. app/v3's 93d4b7f restored dev's ownership rules: an expired hold admits nothing, reopening acquires a new fence, and a take needs the exact fence. The merge keeps those rules for every hold except one this window released itself after its saved state reached the sync service. Only that window may retake the chat when new work arrives in it. It does so by conditionally acquiring a new fence, with the saved head unchanged, before any write. It never heartbeats the released fence. A lapse that was not a release keeps app/v3's behaviour. 93d4b7f's own regression tests pass (TestReleasedSameFenceRetiresReceiptUntilContinuation, TestAnExpiredHoldCannotAuthorizeTools, TestStartupAcquiresNewHold, TestIdleExpiredHoldRequiresContinuation, TestIdleWatchReconnectAndProcessReacquisition, TestRelayOutageO4FailedIdleRenewalKeepsWorkLocal, TestConditionalAcquireRejectsNewerFenceWithSameHead, TestTakeF2SameHolderSameHeadFenceChange). Other interactions resolved: - Saves at pauses (#2356): release now also waits for the capture and for no unsaved turns. - Chat rows (#2425, #2348): the new rows carry can_open beside working, notes, sharing and move. - Beat state (#2432): a fresh fence resets it. - Opening stamp (#2556): kept with the metadata-write hook. TestIdleOpen_C6_D2 now finds the freed row at the fence the reopened writer holds, because reopening takes a new fence on app/v3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eased one app/v3's ownership rules say a heartbeat never revives retired work permission. A window that released its own idle hold could still see its lease live at the released fence, after a stray heartbeat or when the release reply was lost. ConfirmWork used to treat that as held. A self-released drive now always retakes through the conditional acquire, which advances it to a new fence, so new work never runs under the fence it gave up. TestIdleRelease_93d4_SelfReleasedRetakeIsANewFenceNeverARevival is built from 93d4b7f's TestReleasedSameFenceRetiresReceiptUntilContinuation. It checks that a self-release closes the gate and sends no heartbeat at the released fence, that an outside heartbeat there does not reopen it, and that new work takes a strictly newer fence. It also checks that a chat another device took is refused, and that a merely lapsed or outside-released drive behaves exactly as 93d4b7f requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…still asks Every page that described Continue here or taking a chat over now says what this branch does: - A chat no computer holds opens directly on another paired computer, from terminal Home or through agents.open. - A live hold still offers Move here or Continue here. - An idle chat lets go about two seconds after it goes idle, once everything is on the sync service. - New work in the same window takes it back. - A hold that merely lapsed admits nothing until continued. The merged "Home lists chats" section no longer repeats its held-on paragraph. The change entry says sync service and states that app/v3's ownership rules still hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agents.take reported every ownership conflict as "another device continued this chat first". With idle release, the computer B saw holding a chat can take its own chat back at a newer fence while B's take is on the way, and B was then told a device that does not exist had taken it. The app now reads the record again after the refusal. When the holder is still the computer B saw, at a newer fence, the refusal says "<that computer> picked this chat up again", the sentence the terminal already used, now one constant for both. It keeps the conflict code and the holder_changed detail. A different holder keeps the old refusal, and nothing is taken in either case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "Continue <device>'s latest chat here?" rescue is for a computer that is away. It stood when presence said the device was offline, or when the chat's hold had lapsed. On app/v3 an online computer keeps renewing, so the two rarely disagreed. Now an idle chat on a paired computer releases its hold, and an online computer's idle chats read as lapsed. While the change feed is up, only presence decides, so an online computer's chats are never offered. Without the feed, a lapsed or released hold still stands in for the device being away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…offer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…talled After a sync outage, a chat taken back from Home could not send until its engine restarted. The new writer idle-released before the first send, and RenewalStatus then reported it the way it reports a writer that never installed: no period, not holding. Home keeps the taken chat's startup wait until that first send, so every Enter waited for an installation that had already happened. It never reached the ownership read and the conditional acquire that would take the chat back. A writer that released its own hold now keeps reporting its period with holding false, so the send runs the ordinary bounded ownership check and acquires a new fence. Stopped, lost and displaced writers still report no active writer. TestIdleRelease_R4_RA_TakenBackReleasedWriterRemainsInstalled runs the real continuation path. A releases, B opens and releases, A takes the chat back and releases before new work. The test checks the status, the new fence, the transcript and both windows' gates, and it failed before the fix. The shared idle fixture's seed message gets a valid timestamp, so knowledge capture stops re-reading it during certification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A move leaves out untracked files that Git ignores, such as node_modules in a Git work folder. Outside a Git repository every file copies, subject to the existing secret screening and exclusions, as it always has. The pages said rebuildable folders were left out in general. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… hold" This reverts 89871e2 and its manual and change-entry sentences. The designed rule offers a computer's latest chat when the computer is offline or has let go of the chat, and an idle-released chat has let go. The copied-chat journeys (TestCopiedChatPingPongsThroughThreeTakes, TestCopiedChatFromDeepWorkspaceMovesAndReturns, TestCopiedChatTakeRepairsDetachedAdmissionBinding) rely on that offer, with alt+c, while the other computer is online. A two-computer check showed the offer behaves the same on app/v3, so there was no regression to fix here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts 2aa6cd7. The pages were already right. A move leaves out an install folder only when the lockfile that rebuilds it travels too, Git tracks nothing inside it, and .furrowpolicy does not already exclude it. A folder with no lockfile always travels, and Git-ignored untracked output stays behind (cellstore/rebuild.go, cellstore/ignored.go). The rewrite claimed a lockfile alone does not exclude a folder, which is false, and it broke the manual probes that ask why node_modules is missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a restart, Home decided how to open this computer's own local copy of a chat from its display rows alone. Those rows could be empty, or still show this computer's old hold, until the directory follower caught up. A chat another computer had since released then opened as a stale viewer instead of being taken. Home now reads ownership through the send source before opening a local copy, and a free shared row goes down the same conditional free-open road as a remote row. TestIdleOpen_R5_FirstOpenAfterRestartTakesFreeChat covers released and lapsed holds, with and without a stale own-hold display, and failed in all four cases before the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The restart fix read ownership through the directory before every Home open of a local copy on a paired home. On a read error it refused to open the chat at all, after up to 25 s, so a paired computer could not open its own saved chats offline. The read now happens only when it can change the outcome: when this window has no writer or viewer for the chat, and Home's display has no row for it or still shows this computer's own stale hold. It is bounded to 2 s, and any error or timeout opens the local chat exactly as before. Unpaired and single-device homes never read. Tests: an unreachable sync service still opens the local chat; a display that already answers makes no read; an unpaired home never reads. All three failed against the previous commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestIdleOpen_R5_FirstOpenAfterRestart... contained the retired name as a substring, which internal/namelaw rightly refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestIdleOpen_R5b_LocalOpenWithUnreachableSyncOpensLocal, TestIdleOpen_R5b_LocalOpenUsesDisplayWhenItAnswers and TestIdleOpen_R5b_UnpairedNeverReads belong to dee1cd1; the file was not staged with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R5 sent this computer's own released chat down the free-open take, which needs the sync service. Offline, that take neither answered nor said anything, so Enter on one of your own saved chats did nothing. Now: - While Home's machine reading is down or failing, a local line opens through the ordinary local door, as it did before. - A free-open of a row that has a local copy falls back to it. - A free-open take whose bounded first read (2 s) cannot reach the service also opens the local copy. - A remote row offline gets the unreachable sentence, never silence. Online, an own released chat still takes a new fence through the free road. Tests cover: your own released chat opens locally when sync is down; a remote row offline says why; online it still takes a new fence; a local copy keeps the take-race refusal; the free take bounds its first read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ResumeStoppedTurn asked the work gate before checking whether any turn was owed, so a window whose admission refused printed "the reply stopped when this conversation moved — asking again" and then the refusal, with nothing to resume. That happened on app/v3 for a chat held elsewhere. With idle release it also happened for your own released chat opened offline. The owed turn is now checked first. A refused owed turn says the refusal once, without the asking-again line, and stays owed for a later attempt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hold that merely lapsed, for example during a long outage while its computer was mid-turn, opened on another computer with no question. Base asks Continue here in exactly that case, and the holder's turn quietly forked. Now only an explicit idle release (expires 0) opens directly. Every lapsed hold keeps the Continue-here card, because older holders do not always say whether they were working. In a self-released window with the sync service unreachable, a new send says the chat cannot be taken back until the service answers, and keeps the words in the box. TestIdleRelease_R6_LapsedHolderIsFencedAfterAnOpenElsewhere proves the lapsed holder is fenced: once its lease has expired, A admits nothing new. B's open takes a new fence. A's old-fence publish is refused, and B's head is unchanged. A's local turns are kept, and its next send is refused as held elsewhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nue here Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 10, 2026
… before With idle release, a chat that went quiet before an outage has let go of its hold. Sending in it needs one directory read, and that read replayed Home's cached refusal until the next machine reading, for up to about 20 s after the service was back. The live-hold road never needed the read. In a window with no live hold that is not displaced, Enter now makes one fresh, 3-second read instead of replaying the cached failure. It retakes at a new fence when the service answers, and refreshes Home's reading. When the service is really down it prints the take-back line and keeps the words. Live-held windows keep their offline admission unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reopening a chat whose old writer no longer held its hold closed that writer first. When the close's final save failed, for example offline, the old, stopped writer was handed back, and the window answered "this window only shows the chat" until a restart. A fresh writer is now installed, and the failed save stays in the final-save receipt. The same path exists on dev (#2581); idle release made it common, because a self-released writer counts as not holding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lock-defer law requires every critical section to unlock from a defer, so an absorbed panic cannot leave the drive locked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the same fence When the sync service refused the connection, an idle release had already retired the send receipt, set the viewer to Available and stopped renewals, so a chat this computer still held live at its fence could not send after a short outage. A release that provably never reached the service (a failed dial) is now fully undone: the receipt, viewer and renewals return and the next heartbeat renews the same fence. An uncertain failure stays self-released and never revives the old fence; the next send retakes it at a new fence. Idle release makes one request and leaves retries to its own backoff, so a refused dial is not hidden behind a retry deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nstead of asking A window whose writer was not holding, while the fresh reading showed this computer's own live lease at the window's fence, fell through to the Continue here card about its own chat and never asked its writer. Such a row now goes to the writer's own work check, which resumes the same fence or conditionally retakes it. The card still appears when another computer holds the chat or a newer fence from this computer does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An idle chat on a paired computer now hands its hold to the sync service. Opening it on another paired computer takes it over without the Continue-here step, even when the first computer is off. A running chat still asks, so you can watch it without taking it.
Base is
app/v3. The desktop engine and the take path this builds on live onapp/v3. Whenapp/v3lands ondev, this change goes with it, and if it is still open then, the PR re-points todev. The branch is up to date withapp/v3a687c92, merged rather than rebased. Before it merges, it is also tested in a local merge on theapp/v3→devlanding candidate. That merge is not part of this PR.Desktop half: Agent-Field/codeaf-desk#102 opens a
can_openrow directly.What changes
app/v3saves the work folder at pauses (Save the work folder at pauses, not after every tool batch #2356).!shell) takes the chat back before any write or model call.node_modulesstay.can_open.agents.openwith its id, takes it with a non-forcing conditional acquire, checked again at acquisition.file name too long. Names are kept and capped at 240 bytes with a stable hash.How this fits
app/v3's ownership rules (93d4b7f)app/v3's 93d4b7f ("Restore dev ownership expiry and exact-fence consent", part of the #2291 dev merge) replaced the earlierapp/v3behaviour, where an idle expired hold was renewed with the same fence by heartbeat. Its rules:This PR keeps those rules for every hold but one:
app/v3's path unchanged: it is available, and the person continues it.93d4b7f's own regression tests pass on this branch:
TestReleasedSameFenceRetiresReceiptUntilContinuationTestAnExpiredHoldCannotAuthorizeToolsTestIdleExpiredHoldRequiresContinuationTestStartupAcquiresNewHoldTestIdleWatchReconnectAndProcessReacquisitionTestRelayOutageO4FailedIdleRenewalKeepsWorkLocalTestAvailableAfterALapseRetiresTheReceiptTestConditionalAcquireRejectsNewerFenceWithSameHeadTestTakeF2SameHolderSameHeadFenceChangeTestHostedTakeBackReachesAcquireWithoutSharedUploadsOrReleaseDelayTestIdleRelease_93d4_SelfReleasedRetakeIsANewFenceNeverARevivalis built from the first of them. It checks that:Nothing changes for anyone else
can_openis optional andomitempty. The lease rules, wire, directory protocol and replicated records are unchanged.Validation contract
app/v3.Each item maps to
TestIdleRelease_C*/TestIdleOpen_C*tests in cellsync, cellstore, session, syncsetup, chatlist, handoff, tui3 and cmd/codeaf.Verification
CI: the PR gate is green on every pushed head. Full check is green except
TestAServedFactoryStartsAGoAheadsJobWithNoAppAttached, which is pre-existing onapp/v3and fixed on the landing candidate. Other reds seen along the way are pre-existing on base or proved to be flakes:TestMemoryLateReceiptReachesFactoryHeaderAfterQuit(A factory memory receipt that lands after Quit is missing from the ledger or the header (TestMemoryLateReceiptReachesFactoryHeaderAfterQuit fails ~1 in 3) #2566),TestFactoryRetryActionsDoNotReportStoppedand the factory memory pause tests fail on base too, and a loop-speed timing law matched base in interleaved runs.Mac two-home hand test (macOS arm64), throwaway homes, local sync service and stub model, base
app/v3a687c92 against this branch:node_modulesstays behind. Outside Git it travels, the same as on base, because the install-folder rule needs a lockfile that travels.Found and fixed during the hand tests:
Drive.RenewalStatusreported a self-released writer as never installed. It now keeps its period with holding false. Covered byTestIdleRelease_R4_RA_…; the Mac reran 15 of 15 sends and Linux 5 of 5.TestIdleRelease_93d4_…).<computer> picked this chat up againwhen that same computer took its own chat back, instead of blaming another device.TestIdleOpen_R5_…). The Mac measured 0.7–0.8 s to hold.TestIdleOpen_R5b_…,TestIdleOpen_R5c_…).TestR6_…). This also removes the same noise onapp/v3for chats held elsewhere.TestIdleOpen_R6_…).TestIdleRelease_R6_LapsedHolderIsFencedAfterAnOpenElsewhereproves the fencing: the expired holder admits nothing, the open takes a new fence, an old-fence publish is refused, and local turns are kept.app/v3. It no longer replays a cached refusal (TestIdleOpen_R7_…).TestIdleRelease_R7_…; the same path exists on dev, A chat reopened after its writer's close failed stays 'this window only shows the chat' until restart #2581).Performance (Mac, 10 open chats, 3 runs each, equal homes):
Focused tests after the merge:
app/v3's newer sync tests;internal/manualpackage;make test-quick.Landing candidate. In a local merge with
land/app-v3, with dev's hosted sync storage opt-in (Hosted relay storage is off until an account is approved #2509):That needs three dev-only guards, which go into the landing merge rather than this PR.
Pending before merge:
--atand moving a running chat passed.Evidence that a take never admits a stale publish (Mac, local sync service, chat taken while its holder was mid-turn): the relay log shows the holder's last publish and beat at fence 1 (10:10:46.919/.931), then the taker's forced acquire expecting fence 1 → fence 2 (10:10:48.561), a conditional acquire → fence 3, its publish at fence 3 and its release. There is no entry from the old holder after the acquire, and no 409 or 5xx. The taker's continue fetched the holder's last result before acquiring.
Issues found along the way and filed: #2563 (the stub answers codeaf's helpers note), #2564 (the arrival brief swallows typing), #2569 (approval waits for the 10-minute permission refresh), #2574 (offline Enter on a remote row gives no answer), #2579 (copy-folder names grow per move on
app/v3and the landing candidate; fixed here), #2580 (a cached refusal replayed after recovery, on other roads), #2581 (a reopened chat after a failed close; fixed here). Full-check flakes seen:TestSteersLandInTheOrderTheyWereSentpasses 5/5 on head and base (recorded on #1749).Also noted: on first launch,
codeaf chatcontinues another computer's latest released chat automatically. That is base behaviour, seen on dev's build too.Not in this PR
🤖 Generated with Claude Code