feat(handoff): continue threads across environments with any provider - #52
Conversation
Thread transfer impact
This comment will update automatically after the next completed run. |
BarretoDiego
left a comment
There was a problem hiding this comment.
AI-assisted review
The handoff archive path accepts an unrestricted handoff ID, allowing path traversal outside the intended state directory.
The changed tests do not verify that the conversation archive is delivered to or consumed by the destination provider. No production defect is proven from the supplied diff alone.
The context handoff loses the explicitly selected destination model when the destination session is recreated after restart.
No actionable issues found in the supplied changed files.
No actionable correctness, security, performance, or testing findings were identified in the supplied changed-file diff.
| async function ownedDirectory(stateDir: string, handoffId: ThreadHandoffId, create: boolean) { | ||
| const base = await NodeFSP.realpath(stateDir); | ||
| let directory = base; | ||
| for (const segment of ["handoff-context", handoffId]) { |
There was a problem hiding this comment.
major: Validate handoff IDs before using them as filesystem path segments
ownedDirectory joins the caller-provided handoffId directly into a filesystem path. Because the reference decoder does not restrict the ID to a safe single path segment, a value such as ../../outside can make context installation, loading, or removal operate outside stateDir. The existing test at ConversationHandoffContext.test.ts:135 demonstrates that traversal-form IDs are accepted, although its ../outside value only escapes the handoff-context subdirectory.
| const configuration = yield* Effect.try({ | ||
| try: () => latestConfiguration(mapped), | ||
| catch: normalize, | ||
| }); |
There was a problem hiding this comment.
major: Context restart ignores the selected destination model
When a context-mode session is recreated after restart, this call builds modelSelection from configuration.modelSelection rather than pending.destination.modelSelection. The destination may therefore start with the source model, or fail when that model is unavailable on the destination provider, despite the destination model having been validated during preparation.
| Effect.gen(function* () { | ||
| const f = yield* makeTwoServices(provider); | ||
| yield* f.prepare; | ||
| const verified = yield* f.destinationService.handle({ |
There was a problem hiding this comment.
minor: Context tests do not verify conversation archive delivery
The six context-handoff cases assert that the destination has no native session ID and that a new cursor is used, but never assert that the complete conversation archive is passed to or consumed by the destination provider.
Codex threads had no visible handoff action because the existing transfer only supported Claude native sessions. Users can now move a thread to another environment with any registered provider, including Codex → OpenCode, and explicitly select the destination model.
The client distinguishes Native session (compatible Claude sessions) from Conversation context (a new provider-native session with the complete T3 conversation). This change prioritizes transfer between environments; switching providers within one environment remains separate work.
Behavior and architecture
Validation
UND_ERR_SOCKETinstead of its expected 500 response; reproduced independently in the pre-changefeature/thread-handoffworktree atdbeafa2b26as well as this branch. No Project Sync HTTP/framing implementation changed in this PR.Limitations
Native session migration remains Claude-only. Context transfer does not preserve another provider's internal compaction/tool state; large archives require the destination agent to read the provided file. Destination credentials are independent. Existing restrictions remain: attachments, return to an environment containing an earlier copy, and automatic project cloning are blocked. Runtime processes/local services are not migrated.
Provider execution was tested with fakes; no two-physical-machine transfer with real providers or native mobile simulator run was performed. Phase timing instrumentation exists, but no production-network latency claim is made. Clients and both environment servers must be updated; creating/merging this PR does not replace an already running Nightly binary.
Implemented with GPT-6 through the Codex harness.