fix(conversations): a co-tenant follows a replacement only if it declared the same identity - #1801
Merged
Merged
Conversation
jhgaylor
force-pushed
the
stack/1636-apply-isolation
branch
from
September 10, 2026 11:10
e5e1020 to
37403f5
Compare
jhgaylor
force-pushed
the
stack/1636-cotenant-identity
branch
from
September 10, 2026 11:10
7d2a630 to
b31c7cc
Compare
jhgaylor
force-pushed
the
stack/1636-apply-isolation
branch
from
September 10, 2026 11:26
37403f5 to
71809c1
Compare
jhgaylor
force-pushed
the
stack/1636-cotenant-identity
branch
from
September 10, 2026 11:26
b31c7cc to
f8ab821
Compare
jhgaylor
force-pushed
the
stack/1636-apply-isolation
branch
from
September 10, 2026 11:34
71809c1 to
47394bc
Compare
…ared the same identity (#1636) When a sandbox is gone and a conversation wakes onto a replacement, `move_cotenants/3` repointed every live conversation that shared the old machine at the new one, without looking at what those conversations declared. The replacement was built from the *waking* conversation's environment and vault, so a co-tenant that named a different pair ran on another conversation's environment files and vault material while its own row said something else. Which binding won depended on which conversation happened to wake first. Co-tenants normally share one identity, because attaching to a machine requires the same agent, environment and vault. They can diverge afterwards, and the next change in this series is what makes them diverge: rebinding a teammate moves one conversation's environment or vault and leaves its co-tenants where they were. This is the invariant that rebinding needs, landed on its own. `_unsafe_list_cotenants_with_identity/2` reads each co-tenant's effective pair — the conversation's own override, falling back to its agent's environment — which is what a sandbox row carries and what `_unsafe_find_home/4` keys on. Only the co-tenants that match the replacement follow onto it. One that declared something else keeps pointing at the retired row, which its own next wake reads as `:create_new` and builds from its own pair. Every co-tenant is still told the disk is gone, because for every one of them it is. The ones left behind are told why they did not follow. The three new cases were checked against the old `move_cotenants/3` and fail there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQyc3wWWDAeZJE1Vr6etQa
jhgaylor
force-pushed
the
stack/1636-cotenant-identity
branch
from
September 10, 2026 11:34
f8ab821 to
47a6c28
Compare
This was referenced Sep 10, 2026
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.
When a sandbox is gone and a conversation wakes onto a replacement,
move_cotenants/3repointed every live conversation that shared the oldmachine at the new one. It did that without looking at what those
conversations declared. The replacement was built from the waking
conversation's environment and vault, so a co-tenant that named a different
pair ended up running on another conversation's environment files and vault
material, while its own row said something else. Which binding won depended
on which conversation happened to wake first.
Co-tenants normally share one identity, because attaching to a machine
requires the same agent, environment and vault. They can diverge afterwards,
and the next PR in this stack is what makes them diverge: rebinding a teammate
moves one conversation's environment or vault and leaves its co-tenants
where they were. This is the invariant that rebinding needs, landed on its
own so the change to the wake path can be read by itself.
_unsafe_list_cotenants_with_identity/2reads each co-tenant's effectivepair — the conversation's own override, falling back to its agent's
environment — which is what a sandbox row carries and what
_unsafe_find_home/4keys on. Only the co-tenants that match the replacementfollow onto it. One that declared something else keeps pointing at the retired
row, which its own next wake reads as
:create_newand builds from its ownpair.
Every co-tenant is still told the disk is gone, because for every one of them
it is. The ones left behind are told why they did not follow.
Third of eight PRs toward #1636. #1675 is the combined branch and stays open
as the reference until the stack is in.
Validation:
mix precommit. The three new cases were checked against the oldmove_cotenants/3and fail there.The stack (each PR is based on the one above it; review and merge top down):
stack/1680-unchanged-verdict— a save that changes nothing records nothing (Bulk apply reportsupdatedfor a document that changed nothing, so a client cannot tell a no-op from a write #1680)stack/1636-apply-isolation— a raise in one apply document fails that rowstack/1636-cotenant-identity— a co-tenant follows a replacement only if it declared the same identity ← this onestack/1636-update-teammate— Team.update_teammate/4stack/1636-cli-group-by-kind— CLI: group apply documents by kindstack/1636-teammate-kind— apply reconciles Teammate documentsstack/1636-schedule-kind— apply reconciles Schedule documentsstack/1636-webhook-kind— apply reconciles Webhook documents (Bulk apply: accept Teammate, Schedule and Webhook documents so one manifest reconciles the whole estate #1636)🤖 Generated with Claude Code
https://claude.ai/code/session_01WQyc3wWWDAeZJE1Vr6etQa