Repository navigation
An idle chat on a paired computer can hand its hold to the relay (opt-in idle handoff) - #2287
Merged
Merged
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 <[email protected]>
…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 <[email protected]>
…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 <[email protected]>
…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 <[email protected]>
…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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…2283) * test: reproduce lost job ownership and duplicated call receipts Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix: account for durable job ownership and journal call identity Competing manager opens remain owned after Talk selects another journal. Recognize the saved owner for live budget admission and spending. Match receipt rebuilds to the journal identity as well as the cell identity. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: cover factory identity collisions and seeded session names Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix: keep factory budget ownership and seeded identities precise Validate durable ownership against the candidate store. A local worker mirror must share the owning floor manager path before taking an extra manager charge. Seed journal headers and titles from the session folder metadata in either layout. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: racing asks make one manager Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: one manager per job, with a lock across create and join Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs: one manager per job Co-Authored-By: Claude Opus 5.5 <[email protected]> * changelog: name the pull request Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: the mint lock wraps the work, so no closer is handed out Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: reproduce foreign manager charging a local budget Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix: bind manager budget ownership to the issue and creation time Old ownership marks keep their root, job and repository matching. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: reproduce duplicate aliases hiding a new receipt Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix: count saved calls once across ledger session aliases Match call facts across aliases, retaining repeated calls under a single alias and leaving historical rows untouched. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: reproduce unrelated mint blocked by recipe network read Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: reproduce remote Talk reusing an identical local row Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix: read recipes before locking manager minting Keep the held-manager fast path and recheck after locking. Network reads no longer hold the floor-wide mint lock. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix: keep remote Talk on the worker mirror GitHub issues supply Created (github/source.go issueItem); Store.Create preserves it and api transport carries it. Independent factories can therefore share ID, Repo, Num and Created. The remote door must use its mirror; owned runners still consult their floor. Remote manager deduplication remains unsupported. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: verify receipt refresh through the exported handoff seam Assert historical ledger bytes remain unchanged and repeated refresh imports no saved call twice. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: distinguish saved calls by time and row identity Exercise exported Refresh with separate equal-cost calls, microdollar rounding, and repeated ledger IDs. Normalize Go formatting. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: cover both owner identity fields and legacy marks Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix: match saved calls against distinct ledger row identities Match each alias to saved calls in order, using receipt precision for cost and allowing live usage to precede its seal. Count repeated row IDs once and append only missing calls, preserving historical ledger bytes. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix: preserve legacy aggregate call identity and finish formatting Record the legacy prefix assumption for calls sharing a seal. Final review retains no blocking findings. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: a remote job's conversation works in the floor's folder Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: a remote job's conversation keeps the floor's folder Co-Authored-By: Claude Opus 5.5 <[email protected]> * cellindex: count a chat's held rows by its fuller id, so nothing is booked again Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: rows under both ids are one prefix, as the rebuild writes them Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
PR #2099 integrated these changes as a single-parent commit. Preserve its app/v3 behavior while recording the 49 dev commits as merged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep later app/v3 work, preserving the true dev merge and the published #2099 behavior choices. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Retain app/v3 solo-chat pairing, ordinary effort repair and opt-in team sharing. Adapt newer team sharing safeguards and replacement receipts to SQLite and durable pairing recovery. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve opt-in sharing, adapt SQLite fixtures and first-sharing backups, and carry replacement receipts through adoption retries. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test: expose unpublished evidence when memory resume stalls Co-Authored-By: Claude Opus 5.5 <[email protected]> * scratch: distinguish memory snapshot timing from interrupted passes Co-Authored-By: Claude Opus 5.5 <[email protected]> * scratch: complete memory admission trace import Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: hold resumed memory while the last stage finishes Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: isolate a successful resumed pass from job-end cancellation Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: observe resume after trailing journal capture Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: reproduce trailing evidence through public history admission Remove scratch traces after identifying the two later manager timeline sources. Classify fixture requests by their provider role so brief errands do not count as memory. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: wait for resumed memory citations instead of future job evidence The owned pass publishes its fixed input before the runner writes final timeline messages. Those later sources can raise profile-wide Pending again without losing retained evidence. Freeze the expected sources before releasing the resumed turn and wait for their published citations. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test: require trailing pending evidence in the resume regression Co-Authored-By: Claude Opus 5.5 <[email protected]> * changelog: name the pull request Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
* Turn host pressure off in the E13 B2 recovery test TestAppBackgroundE13B2QueuedTaskAcquiresAndSettlesAtStartup waited 30 s for a queued task to settle, but the admission governor holds a queued task while the host's load is at or over 1.5 per core or memory is under the floor. Under a full test run the host stays busy and the task stays queued, so the wait ran out. The governor is doing its job. The test now turns both ceilings off through the settings a person uses. The wait and every assertion are unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Add the change note for the E13 B2 test Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
…tches (#2196) * app tests: a fake lane holds an event until it is watched (red) Co-Authored-By: Claude Opus 5.5 <[email protected]> * app tests: a fake's lane holds what is put on it until the backend watches The backend publishes an agent as live before it starts watching its lanes, so a test's first raise or lane could read a nil channel and block for ever, which hung all of cmd/codeaf until the timeout. Events put before the watch are now held and handed over in order when the lane opens. Co-Authored-By: Claude Opus 5.5 <[email protected]> * changes: entry for the held fake lane (#2196) Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
…st (#2181) * tests: R2 and R3 wait for the startup publication instead of chats.list #2031 moved the startup pass that publishes existing chats out of chats.list into a background loop, and TestR1_6InventoryDoesNotWaitForPublication pins that chats.list never waits for it. R2 and R3 still called ListChats to join that pass, so with the real engine they read the directory before anything was published. They now wait for the startup pass, and R3's restart observes one whole settled publication round before checking that nothing is released again. * docs: change entry for this pull request
…chat (#2182) * tests: damage the teams store, not a teams.json, before continuing a chat #2066 moved teams into an SQLite store. TestContinuingAChatKeepsItsLease- DespiteDamagedTeamsAndLaterHeals still damaged teams by overwriting the bytes at teams.Path and later wrote them back, which no longer breaks the store, so the take never met a failed rebind and never said so. It now uses teamstest.Break and its restore, as the other teams tests did in #2099. * docs: change entry for this pull request
…o answer (#2184) * tests: keeping an unsent turn on an ownership check needs the store to answer TestOwnershipCheckThatFindsAnotherHolderKeepsUnsentTurns kept the store blocked through CheckLease, but keeping the turn uploads it as a branch, and Batcher.lost documents that a failed branch stays stale for the next flush to retry. The check could never keep the turn, so the test failed on its premise. It now refuses only A's heartbeats (quiet) and lets the store answer before the check, so only the check can learn of the move and the kept-turn notice is still required by the time it returns. * docs: change entry for this pull request
) * syncsetup: run the takeover proof's sessions with routing off TestTasklessTakePreservesConversationWorkspaceIdentity counted every request to its fake router and failed when one arrived. Its own two session.New calls start the lane-sheet beat, whose first act is GET /models/<model>/endpoints, racing the immediate Close. When the beat won, the test failed with "taskless takeover unexpectedly called a provider". Every caught request was that GET, with no body, made while the test was in session.New or Close; none during Take. The sessions now run with RoutingOff, which refuses the beat, so any request still fails the test and means something new called the router. * changelog: takeover proof routing off * changelog: shorter title
* Run the clock while a learner waits for a state write TestR26R27C30OpenLearnerIdleAndStateWake froze its fake clock after the idle count, then waited for learner a to apply the next write. A learner's transport lane holds two messages. When it is full, the message carrying the new commit is dropped and raft counts it as sent, so only the next heartbeat delivers it. With no tick, no heartbeat came, and the wait ran out at 30 s. Advance the clock at its real rate from the write on. The idle count and every assertion are unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Add the change note for the learner wait clock Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
* Check pairing copy around the random check words TestM1M4M8M9M10AppPair banned engine words by substring across every visible line, including the three random check words. The word list holds "ledger" and "cello", which contains "cell", so about 2.3% of pairings failed the check. Remove the pairing's own words from the line before the ban check. All other copy is still checked. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Add the change note for the pairing copy test Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
Team sharing stays an explicit opt-in on app/v3. Two lines carried from dev still described it as on by default. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Seed unrepaired rows through teamstest and verify persisted repairs with a non-repairing snapshot. Enable sharing explicitly before checking deferred pairing reads after Settings saves and completed turns. Both product promises already work; preserve the default-off preference. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
…nue here Co-Authored-By: Claude Opus 5.5 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
…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 <[email protected]>
…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 <[email protected]>
…s, publishes or resumes in place ConfirmWork and the terminal's work check now share one decision over a fresh directory reading. A hold this drive still validly holds sends on its fence. Once a lapse or an outside release is observed, the old fence is retired: it no longer renews or publishes, so a later heartbeat cannot revive a writer that has lost its permission. Such a drive refuses work, as 93d4b7f requires, and the chat continues only through a conditional take into a fresh drive. A window that released its own hold still reacquires in place at a new fence. Another holder, or any newer fence, still needs consent. The take road's already-here shortcut now needs this computer's matching send receipt, so a retired writer takes the ordinary conditional road, carrying its unpublished turns. A table-driven test covers every reachable combination of the relay's view, the drive's state, the service's answer, the turn and unpublished local turns, with impossible cells named. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…, and a refused send keeps its words A window whose own hold lapsed, or was released from outside, sends its first Enter through a conditional take into a fresh writer, carrying the draft, with no card and no trip to Home. The card appears only when another computer holds the chat or has taken it. A send the engine refused used to leave the message box empty, because the box is cleared before the asynchronous submit; the refused words now go back ahead of any newer draft, on the plain and attachment roads. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…again, and recovery order has one outcome A retired fence refused every drain, so a call that was already running when the hold expired could neither publish nor go to a kept branch, which 87c7f49's late-seal orders require. Retirement now stops renewal beats and new work only; already sealed turns take the ordinary publish or resolve road, where the relay fences them: an unchanged expired fence may renew through that late publish, and a newer holder's fence sends them to a kept branch. The state matrix gains a call in flight when the hold expires, and the order of events after an outage, each cell with one expected outcome: a renewal that reaches the unchanged hold first keeps the fence and uploads local turns; an ownership check that sees the expiry first keeps pending work on a branch; a work check that sees it first lets sealed work drain without reopening work. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…d move or a stale held status A failed move from Home to another chat marked the current chat blocked as well, and that mark survived the outage, so both Enters replayed the cached reading and nothing was sent after recovery. A stale writer status that still claimed the retired hold hid the recovery check the same way. Enter now checks again whenever no local hold is admitted, a blocked own fence recovers through the conditional reopen, and the box keeps its words until the send succeeds. Admission also waits for a late writer-status update. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…-in) Port of #2287 onto dev. The new behaviours sit behind one setting, `idle handoff` (CODEAF_IDLE_HANDOFF), which is off by default: with it unset a paired computer behaves exactly as before, idle chats keep renewing their hold and a released remote chat still needs Continue here. With it on, an idle chat in a paired set releases its hold to the relay after its upload, and a free shared chat opens and acquires on Home and through agents.open. The defect corrections from the PR stay on by default: refused words go back into the box, a refused owed reply keeps its question, an offline send names the unreachable service. Also fixes the three defects the owner's hand tests found on the PR: a `!` shell line now takes the same ownership check as a plain message; Enter on a chat whose orphaned turns were kept in a branch reopens the original chat; a running turn whose own hold lapsed reacquires it in place when no other computer holds the chat. Co-Authored-By: Claude Opus 5.5 <[email protected]>
In the other-holder cells with unsaved turns, the matrix calls ConfirmWork a second time after draining the late seal. That call starts the drive's background forget-then-notice step again, and the subtest returned before it finished, so TempDir cleanup raced its hold-file lock (3 failures in 40). The cell now waits for that step's final notice, as the consent and unreachable branches already do. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Its hint and an alias said "paired", so typing "pair" in settings landed on General instead of Devices (TestSettingsDevicesSearchFindsTheTabAcrossQueries). The row now says "your other computers" instead. Co-Authored-By: Claude Opus 5.5 <[email protected]>
… on dev Two places in the port's send check diverged from dev with the setting off: - A storage-off chat has no uploading writer, so the new take-back road waited the full take-back time for one and then refused. The words left the box with nothing said. A local, unheld reading now skips that road, as dev did. - An own expired hold whose saved head differs from this window's continued in place without asking. That widening now needs idle handoff on or the reopen road. Otherwise Continue here asks, as on dev. Restores four dev tests in cmd/codeaf/storage_off_lifetime_test.go. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbirAbbas
marked this pull request as ready for review
November 14, 2026 15:19
AbirAbbas
added a commit
that referenced
this pull request
Nov 14, 2026
…in (#2644) #2287 added a SubmitBash method to the shared switchAgent test fake. It shadowed fakeAgent.SubmitBash, which records the line, so TestUserBashHomeRunsInTheSelectedProject failed on dev. The method is removed; no product code changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
Ported from app/v3 onto
devafter app/v3 landed (#2561). This branch keeps the original commits in its history; the last commit is the port onto today's dev.Off by default
The new behaviour sits behind one setting, idle handoff (
idle_handoffin Settings → General, orCODEAF_IDLE_HANDOFF=1). It is off by default. With it unset, a paired computer behaves exactly as on dev today:agents.opennever acquires a shared chat on its own.With it on (and hosted sync storage allowed):
agents.open(can_open) without a Continue-here step.A live hold or a lapsed hold still asks in both cases.
What stays on by default
These change dev's behaviour only where dev was wrong:
!command the engine refuses puts the words back in the box.Three defects from the hand tests, fixed here
!shell line now runs the same ownership check as a plain message. If it is refused, the line stays in the box and one line explains why.#2381's behaviour on dev is intact.
ConfirmWorkstill retires a noticed expired hold, and a recovering batcher still heartbeats before it publishes. #2643's owed-upload wrap is kept. Dev's storage-off rules (#2509, #2536) gate both new behaviours.Existing deployments
appwiregains one field,can_open, which isomitempty. An older desktop client ignores it. The desktop companion is not part of this change.invalidates:lines each say whether they needidle handoffon.Verification
Every pass below was rerun by the driver after the last edit, not taken from the porting run.
cmd/codeaf/idle_*terminal*_test.go) pass twice, with the setting switched on by the tests themselves and nothing exported, as CI runs them.TestIdleRelease_DefaultOff_*(syncsetup),TestIdleOpen_DefaultOff_*(tui3) andTestIdleHandoff_DefaultOff_*(cmd/codeaf) prove that unset behaves like dev.TestIdleOpen_P9X1_*,TestIdleRelease_P9X2_*,TestIdleOpen_P9X2_*andTestIdleRelease_P9X3_*.TestLateSealOnExpiredUnacquiredHoldDoesNotRenewLease,TestAppRelayOutage*and theTestL4Hosted*/TestHosted*quit tests pass unchanged.internal/syncsetuppasses in full, as dointernal/cellsync,internal/config(see below),internal/manual,internal/chatlistandinternal/appwire.make test-quickpasses.make pr-readywas rerun by name on this branch and on dev6a5f28744. Each one now either fails on dev too or passes on both.TestRegistryCoversEveryUserFacingEnvironmentPin, which Build-then-review pipelines, completion time, watched-run usage, queue refresh, control sockets #2609 broke on dev.The driver's reruns also found three defects in the port, all fixed here:
storage_off_lifetime_test.go.🤖 Generated with Claude Code