Skip to content

Never lock a person out of their own chat on an older sync service - #2075

Merged
AbirAbbas merged 5 commits into
devfrom
fix/2072-never-locked-out
Oct 30, 2026
Merged

AbirAbbas merged 5 commits into
devfrom
fix/2072-never-locked-out

Conversation

@santoshkumarradha

@santoshkumarradha santoshkumarradha commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Part of #2072. This covers items A, B (partly, see Limitations), D, F, G, I, L (the sync-service sentence) and M. Items C, E, J and K are handled with #2070.

What a person sees

Before. On the hosted sync service, I reopened a chat whose hold had lapsed on its only computer, typed a message and pressed enter. The words stayed in the box. The chat showed:

calls are not being sealed, so rewind cannot reach them: the sync service must be updated before chats can move; codeaf's operators update the hosted service, or run this build of codeaf relay for your own service

Each later enter refused again. A second computer could not continue a chat the first had closed. codeaf devices revoke <name> --all said no device called "<name>" is paired with this machine for a computer codeaf devices listed. After that computer was removed, Home still offered Continue <name>'s latest chat here?.

After.

  • A chat with a lapsed hold opens and you can write in it, on every sync service. Enter sends and the reply arrives. The turn goes to your other computers, and there is no warning line and no card.
  • Turns this computer saved while it could not publish are sent when the chat opens. Before, they made the chat read-only behind Continue this chat here? with this chat was last held here.
  • A chat this computer held last, with nobody else holding it, opens without the question.
  • Only one thing waits for an updated service: taking a chat that is open on another computer. That case now says taking an open chat from another computer waits for a sync service update; all else works, so close it there and continue here. Continuing the chat after the other computer closes it works.
  • codeaf devices revoke <name> --all removes every device of that name, whichever kind it is.
  • A removed device is never offered on Home's resume card.
  • If you open a window whose sync setting differs from a running engine, the refusal now offers codeaf chat --no-host. That opens the window on its own and leaves the other window running.
  • The .moving folder that holds the live copies of older chats now has a README.txt. It says the folder holds the chats and must not be deleted.

Code

  • directory.HTTP.Acquire now needs the codeaf-conditional-takes promise only for a forced take, that is, taking from a live holder. A take of a lapsed lease is safe on an older service for three reasons:
    • The service refuses a live holder atomically.
    • The service raises the fence.
    • The caller compares the head that the take returns with the head it saw. The drive gives the lease back if they differ, and the handoff fetches the new head.
  • In syncsetup.driving, a free lease is taken when this computer's newest turn is the record's head or descends from it (new cellstore.Extends). catchUp then publishes those turns. A local head that does not extend the record's head still opens read-only, as before.
  • In chatlist, this device's own lapsed lease reads as Idle instead of Off. Rows carry a new HolderRemoved field, and the resume card skips those rows.
  • directory.Capabilities lists each promise this build needs from the service and the feature that waits on it. syncsetup.ProbeService checks a live service as a throwaway identity: it registers a device, creates an empty chat record, reads it and archives it. codeaf-release service [--url] exits 1 and names any missing promise.
  • devices revoke --all stops the matching device in every kind. It prints what it stopped even if a later kind fails.
  • The engine's different-environment sentence now offers codeaf chat --no-host.
  • cell.CopyLegacy writes README.txt into the copy folder once.
  • Manual: updated the continuing, hosted-or-own relay, relay-refusals, terminal-engine and no-ssh pages. Added a section on the .moving folder.
  • Replaced test: TestReleasedOlderHeadIsNotAcquiredOverLocalWork pinned the law that caused the lockout. TestReleasedHeadIsContinuedWithThisComputersNewerTurns now pins the opposite.

Validation

Red then green

  • TestOldServiceStillLetsTheOnlyComputerWriteItsChat: two homes, with a real relay that strips the conditional-take promise.
  • TestOldServiceLetsLocalTurnsCatchUp
  • TestOldServiceStillTakesALapsedLease
  • TestProbeServiceNamesWhatAnOlderServiceLacks: current service and older service.
  • TestServiceGateFailsAheadOfTheService
  • TestThisComputersLapsedChatOpensWithoutAQuestion
  • TestARowSaysItsHolderWasRemoved
  • The resume case for a removed device.
  • TestDevicesRevokeAllFindsAComputerThatSyncsChats
  • TestTheCopyFolderSaysItIsTheLiveStore

Suites

  • Focused packages pass: internal/directory, chatlist, cell, relayserve, cmd/codeaf-release, the cmd/codeaf devices and engine-environment tests, and the related tui3 tests.
  • make test-packed-manual manual-gates passes.
  • make test-quick passes. make pr-ready: every failure it reports also fails on unchanged origin/dev here (listed below), except cmd/codeaf TestCellRewindRestoresTheWorkspaceTheSessionSealed, which passes alone on both and failed once under the full touched-package load

Pre-existing failures. These tests also fail on unchanged origin/dev on the same machine:

  • internal/cellstore TestStartupInALargeHomeWalksNoHome
  • internal/release TestC16PromotionTriggerAndCutoff, TestPromotionWorkflowExplainsItsSafetyLaws
  • internal/syncsetup cost, scope, death-branch, running-take, public-take and drive-timeout tests
  • internal/syncsetup TestTasklessTakePreservesConversationWorkspaceIdentity fails sometimes on both, after a model-catalog lookup.

Live check against the hosted service as deployed today. Two paired computers. Every model role, including router, title and reflex, ran on deepseek/deepseek-v4.1-flash, as recorded in the transcript.

Check Before (dev) After (this branch)
Reopen a lapsed chat on its computer, send Reply with exactly the word BANANA. words stay in the box; refusal line shown reply BANANA; the service's head equals the local head
Second computer continues a chat the first closed not possible (conditional take refused) chat opens with full history; KIWI sent and answered
Second computer chooses continue here on a chat open on the first — one line: taking an open chat from another computer waits for a sync service update; …
codeaf devices revoke alpha --all no device called "alpha" is paired with this machine alpha was removed — it can no longer sync your chats.
Home after removing alpha Continue alpha's latest chat here? no offer
Second window with CODEAF_SYNC_URL=off while one is open offers only engine --stop offers codeaf chat --no-host; that window opens, lists chats, and GRAPE is answered; the first window keeps running
go run ./cmd/codeaf-release service — exit 1: the sync service at https://codeaf.agentfield.ai/fabric must be updated before this build ships; it does not promise codeaf-conditional-takes (…)

Limitations

  • The release workflow does not call the new gate yet. This branch could not change .github/workflows/release.yml, because the push token has no workflow permission. A maintainer needs to add the job below and make build depend on it (needs: [prepare, test, service], with needs.service.result == 'success' in its if:). Today the job would fail every release, because the hosted service does not answer codeaf-conditional-takes. Deploy the hosted service from current relay/hosted first.
    service:
      needs: prepare
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4
          with: { ref: "${{ needs.prepare.outputs.sha }}" }
        - uses: actions/setup-go@v5
          with: { go-version-file: go.mod, cache: true }
        - run: go run ./cmd/codeaf-release service
  • Taking a chat that is open on another computer still needs the updated service. That limit is deliberate, and the message says what to do instead.
  • One stale card remains after a failed take. If a take into a window fails, the next send in that window still shows the card with this chat was last held here. continue here then works. That send-hold logic is part of Continuing a chat on another machine fails silently and can list it twice #2070's scope.
  • Item H is only partly checked. With sync off, current chats and chats continued from another computer list on Home, open, and accept writes. Older chats that live as .moving copies were not reproduced here.
  • codeaf doctor does not yet report capability gaps, lapsed holds or removed devices.
  • Evidence capture. Terminal captures of each row above were taken from the live UI. They are not attached because this environment cannot upload attachments.

🤖 Generated with Claude Code

santoshkumarradha added a commit that referenced this pull request Oct 30, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@santoshkumarradha
santoshkumarradha marked this pull request as ready for review October 30, 2026 15:35
santoshkumarradha and others added 5 commits October 30, 2026 11:38
A chat whose lease had lapsed could only be written after a conditional take,
which the client allowed only when the service promised codeaf-conditional-takes.
The deployed hosted service does not, so every lapsed chat was refused: the drive
failed, turns were saved locally only, and the next open found this computer's
own newer turns and showed the chat read-only behind "Continue this chat here?".

A take of a lapsed lease is now made on every service: an older service still
refuses a live holder atomically and raises the fence, and the caller checks the
head the acquire answers. Only displacing a live holder waits for the promise.
Turns this computer sealed on top of the record's head are published when the
free lease is taken, instead of opening the chat read-only.

The release workflow now probes the live hosted service as a throwaway identity
and refuses to build when it lacks a capability this build needs
(codeaf-release service). The refusal for the one waiting feature says what
still works and what to do. devices revoke --all now removes every device of the
name, computers that sync chats included.

Part of #2072

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a removed device

A chat whose lease this computer held and let lapse read as off, so Home and
the card asked "Continue this chat here?" on the only computer that used it,
saying "this chat was last held here". With nobody else holding it there is
nothing to continue from: it now reads as free and opens.

A chat whose holder was removed read as released by that device, so the resume
card offered "Continue <removed device>'s latest chat here?". Rows now carry
that the holder was removed, and the card never offers it.

Part of #2072

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The refusal named an operator task and was cut off on a normal terminal. It now
says the one thing that waits, that everything else works, and what to do:
close the chat on the other computer and continue here. The manual describes
the lapsed-chat and older-service behaviour.

Part of #2072

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lder

A terminal whose environment differs from the running engine was offered only
engine --stop, which interrupts the other window. The sentence now offers
codeaf chat --no-host first, which opens this window on its own.

The folder that holds older chats' live copies is named .moving and reads like
a stuck transfer; it now carries a README.txt saying it holds the chats and
must not be deleted.

Part of #2072

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@santoshkumarradha
santoshkumarradha force-pushed the fix/2072-never-locked-out branch from d4d5720 to 854f3cf Compare October 30, 2026 15:41

@AbirAbbas AbirAbbas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hand-tested on two computers through a relay that lacks the conditional-take promise: reopening a released chat accepts writes with no card, the other computer continues it (Moved from desk-a … Everything as you left it.) and writes, and devices revoke --all removes the synced computer.

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