mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-24 22:10:59 +00:00
fix(cli): keep fresh machine-spawn prealloc on adopt-stub
rpcGateway sends sessionId = reservedSessionId ?? existingSessionId, so a fresh Codex spawn arrived with sessionId set. The local reopen fallback then stamped --existing-session-id for the preallocated stub, and Codex exited 1 with "Existing HAPI session has no Codex thread binding". Only an explicit existingSessionId may reopen; reservedSessionId stays on --hapi-session-id. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -430,6 +430,22 @@ describe('buildCliArgs', () => {
|
||||
expect(args).not.toContain('--existing-session-id')
|
||||
})
|
||||
|
||||
it('keeps a fresh prealloc on adopt even when the hub duplicates it as sessionId', () => {
|
||||
// rpcGateway sends sessionId = reservedSessionId ?? existingSessionId.
|
||||
// Treating sessionId as reopen made Codex fail with
|
||||
// "Existing HAPI session has no Codex thread binding" on every new spawn.
|
||||
const prealloc = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee'
|
||||
const args = buildCliArgs('codex', {
|
||||
directory: '/tmp',
|
||||
sessionId: prealloc,
|
||||
reservedSessionId: prealloc,
|
||||
startingMode: 'remote',
|
||||
})
|
||||
expect(args).toContain('--hapi-session-id')
|
||||
expect(args[args.indexOf('--hapi-session-id') + 1]).toBe(prealloc)
|
||||
expect(args).not.toContain('--existing-session-id')
|
||||
})
|
||||
|
||||
it('stamps --existing-session-id for Claude reopen/resume (not adopt --hapi-session-id)', () => {
|
||||
// #1911 Opus Critical: syncEngine resume passes access.sessionId as
|
||||
// existingSessionId; adopt-stub stamp → SessionNotAdoptableError → 409.
|
||||
|
||||
+6
-13
@@ -1995,26 +1995,19 @@ export function buildCliArgs(
|
||||
// Codex shares one engine; Runner owns the wrapper, not a remote mode.
|
||||
if (agent !== 'codex') args.push('--hapi-starting-mode', startingMode);
|
||||
args.push('--started-by', 'runner');
|
||||
// Codex, Cursor ACP, DSH, OpenCode, Pi native resume, and Claude message-level
|
||||
// forks reuse the original HAPI row via --existing-session-id. Grok fork
|
||||
// children also bind the pending HAPI session id.
|
||||
const reuseExistingSessionId = agent === 'codex' || agent === 'cursor' || agent === 'dsh' || agent === 'pi'
|
||||
|| agent === 'opencode'
|
||||
|| agent === 'agy'
|
||||
|| agent === 'grok'
|
||||
|| (agentCommand === 'claude' && options.forkSession);
|
||||
const existingSessionId = options.existingSessionId
|
||||
?? (reuseExistingSessionId ? options.sessionId : undefined);
|
||||
|
||||
// Stamp the HAPI row id on argv for orphan reap after tracking loss (#1910).
|
||||
// Adopt-stub (`--hapi-session-id`) vs reopen (`--existing-session-id`) are
|
||||
// different operations — never collapse them (#1911 Opus Critical + Codex Major).
|
||||
// Local HTTP non-UUID sessionId stays on --hapi-session-id (reap-only; create
|
||||
// ignores non-UUID reserved ids). A UUID sessionId must NOT stamp adopt — that
|
||||
// would 404/409 against a non-stub row (#1911 Opus Major @ 09141964c).
|
||||
// `sessionId` is never reopen on its own: for a fresh machine spawn the hub
|
||||
// passes the preallocated stub as both sessionId and reservedSessionId, and
|
||||
// reopening it would make Codex demand a thread binding that cannot exist yet
|
||||
// ("Existing HAPI session has no Codex thread binding").
|
||||
const hubUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
if (existingSessionId) {
|
||||
args.push('--existing-session-id', existingSessionId);
|
||||
if (options.existingSessionId) {
|
||||
args.push('--existing-session-id', options.existingSessionId);
|
||||
} else if (options.reservedSessionId) {
|
||||
args.push('--hapi-session-id', options.reservedSessionId);
|
||||
} else if (options.sessionId && !hubUuid.test(options.sessionId)) {
|
||||
|
||||
Reference in New Issue
Block a user