Session Adopt Validation and PR Review

Findings
- [P2] Cross-repository adoption rejects legitimate Gemini/OpenCode callers —
cmd/entire/cli/session_adopt.go:164
ResolveCallerSessiononly loads states from the target repository’s session store. During adoption from a different repository, the source state—and its recorded process owner—is therefore unavailable to ancestry resolution. Agents without caller-session environment variables (Gemini CLI and OpenCode) cannot be recognized as owning their own source session, so non-interactive adoption is refused unless they incorrectly use--allow-foreign-session. Include the selected source state in caller resolution, or directly compare its owner against current process ancestry before falling back to the foreign-session path.
Tests run:
Passed. No other findings.
Finding
- [P2] Ambiguous callers can bypass the ownership guard through any ancestor —
cmd/entire/cli/session_adopt.go:197
The fallback runs for every non-IsCallerresolution, includingResolutionCallerAmbiguous. If the source belongs to an outer agent,HasAncestorsucceeds even though an unresolved environment claim may identify a nearer inner agent. Adoption then proceeds silently despite the resolver explicitly declaring ownership ambiguous. The existing ambiguous test misses this because its source has noOwner. Refuse ambiguous resolutions, or only accept the source owner when it is provably nearest (for example, depth zero with the resolver’s claim-ordering rules).
The previous cross-repository Gemini/OpenCode issue is fixed.
Tests run:
Passed.
Finding
- [P2] A nested no-env agent can still adopt an outer agent’s session silently —
cmd/entire/cli/session_adopt.go:212
With no environment claims,HasAncestortreats any matching ancestor as uncontested. Consider OpenCode launching Gemini, with both sessions stored in the source repository while the command runs from a separate target repository. The target-only resolver sees neither state; explicitly selecting OpenCode’s outer session passesHasAncestor, although Gemini’s owner is nearer. Resolve against all relevant source and target states together—or otherwise require the selected source owner to be the nearest recorded owner—instead of checking only whether it appears anywhere in the ancestry.
The previous ambiguous-caller finding is fixed.
Tests passed:
can you review the branch for 2332 again, can you also read the description again and see if it can be improved
Code review
- [P2] Equal-depth sessions are all treated as the caller —
cmd/entire/cli/session_adopt.go:272
sourceOwnerIsNearestonly rejects a strictly nearer owner. A long-lived agent process can own multiple session states at the same ancestry depth, such as an older stale session and a newer resumed session. Selecting the older session therefore passes silently. The existing resolver handles this case by choosing the most recently interacting session (isNearerOwner); this fallback should use equivalent tie-breaking rather than treating every equal-depth state as the caller.
The previous nearer-owner finding is otherwise fixed.
Tests passed:
PR description feedback
The description is now stale:
- “Five new” tests should be ten new tests.
- The Cursor summary still references commit
ed552a050and the original five-test implementation; remove or regenerate it. - Add the important cross-repository behavior:
- Gemini/OpenCode have no session-ID environment variable.
- Source-store owners are therefore ranked by ancestry.
- Nested source sessions require nearest-owner selection.
- Ambiguous resolutions never fall back to ancestry.
- In the behavior table, replace “refused; terminal gets a confirmation” with “requires terminal confirmation; refused without a terminal”, since confirmation can permit the operation.
- Mention that non-interactive refusal now prints the
--allow-foreign-sessionremedy and prompt cancellation uses the shared cancellation handler.
The “Why” and separate-flag rationale are strong and should remain.
Code review
- [P2] Ownership is authorized from an unlocked snapshot —
cmd/entire/cli/session_adopt.go:110
The ownership check runs beforeWithSessionStateLocks/MutateSessionState. Both mutation paths subsequently reload the source state under lock but do not repeat ownership validation. A turn-start or resume can update the source owner—or introduce a nearer source session—between authorization and mutation, allowing adoption based on stale ownership evidence. Revalidate the locked source state and ownership ranking immediately beforebuildAdoptedSessionState; if interactive confirmation happened, ensure the locked state still matches what was confirmed.
The equal-depth recency issue is fixed.
Tests passed:
PR description
It remains substantially stale:
- “Five new” should now account for 13 unit tests and 3 integration tests.
- It omits source-store ancestry ranking, nested no-env agents, equal-depth recency, flag-parser coverage, and real-binary non-interactive coverage.
- The behavior rows should say “requires confirmation in a terminal; refused without one”, not simply “refused.”
- The Cursor summary still describes commit
ed552a050and the original five-test implementation; remove or regenerate it. - Mention that detailed rationale moved to
docs/architecture/session-caller-resolution.md.
The “Why,” existing-check explanation, and separate-flag rationale remain strong.
Updated both:
- PR #2332 description: https://github.com/entireio/cli/pull/2332
- Trail #1273 body: https://entire.io/gh/entireio/cli/trails/1273
Removed the stale Cursor summary and documented current behavior, ancestry ranking, tests, integration coverage, and validation.
mise run lint passed before the remote updates.
Note: uncommitted changes appeared in cmd/entire/cli/session_adopt.go during the update. I left them untouched.