Skip to content

Commit baadb36

Browse files
committed
fix: preserve executor recovery ownership
1 parent b7035a1 commit baadb36

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

scripts/zcode-companion.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@ async function executeReserved(context) {
179179
} catch (error) {
180180
await client?.close().catch(() => {});
181181
const current = await store.readJob(cwd, job.id).catch(() => null);
182-
if (current && !['failed', 'succeeded', 'cancelled', 'cancelling'].includes(current.status)) {
183-
await store.transitionJob(cwd, job.id, [current.status], 'failed', { error: { message: error instanceof Error ? error.message.slice(0, 2048) : 'Execution failed' }, finishedAt: new Date().toISOString(), exitCode: 1 }).catch(() => {});
182+
if (current?.status === 'queued') {
183+
await store.transitionJob(cwd, job.id, ['queued'], 'failed', { error: { message: error instanceof Error ? error.message.slice(0, 2048) : 'Execution failed' }, finishedAt: new Date().toISOString(), exitCode: 1 }).catch(() => {});
184184
}
185185
throw error;
186186
}

tests/recovery.test.mjs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,37 @@ test('foreground executions persist an exact worker lease identity', async (t) =
131131
assert.equal(persisted.childPid, process.pid); assert.match(persisted.workerLeaseId, /^[a-f0-9]{64}$/);
132132
});
133133

134+
test('enclosing foreground execution preserves executor ownership after ambiguous read and stop failure', async (t) => {
135+
for (const stopSucceeds of [false, true]) {
136+
const fixture = await context();
137+
t.after(() => cleanupRecoveryFixture(fixture));
138+
const env = { ...fixture.env, ZCODE_PATH: fakeZCode, FAKE_ZCODE_ERROR: 'session/read', ...(stopSucceeds ? {} : { FAKE_ZCODE_STOP_ERROR_PREFIX: 'session-' }) };
139+
await assert.rejects(runCompanion(['rescue', '--fresh', `ambiguous-${stopSucceeds}`], { cwd: fixture.workspace, env, authorization: { callerContext: fixture.callerContext } }), /fixture request failed/);
140+
const store = createStateStore({ dataRoot: fixture.dataRoot }); const [persisted] = await store.listJobs(fixture.workspace);
141+
assert.equal(persisted.status, stopSucceeds ? 'failed' : 'running');
142+
if (!stopSucceeds) {
143+
assert.match(persisted.lastCancelError, /fixture stop failed/);
144+
await assert.rejects(store.reserveJob({ workspace: fixture.workspace, ownerSessionId: 'owner', ownerTurnId: 'later', command: 'rescue', readOnly: false, permissionSnapshot: { permissionMode: 'workspace-write' } }), { code: 'WRITABLE_JOB_EXISTS' });
145+
}
146+
}
147+
});
148+
149+
test('enclosing background execution preserves executor ownership after boundary callback and stop failure', async (t) => {
150+
for (const stopSucceeds of [false, true]) {
151+
const fixture = await context();
152+
t.after(() => cleanupRecoveryFixture(fixture));
153+
const env = { ...fixture.env, ZCODE_PATH: fakeZCode, ...(stopSucceeds ? {} : { FAKE_ZCODE_STOP_ERROR_PREFIX: 'session-' }) };
154+
const reserved = await runCompanion(['rescue', '--background', '--fresh', `boundary-${stopSucceeds}`], { cwd: fixture.workspace, env, authorization: { callerContext: fixture.callerContext } });
155+
await assert.rejects(runCompanion(reserved.privateInvocation, { cwd: fixture.workspace, env, authorization: { executionCapability: reserved.executionCapability, jobId: reserved.job.id }, startupAck: async () => { throw new Error('boundary callback failed'); } }), /boundary callback failed/);
156+
const store = createStateStore({ dataRoot: fixture.dataRoot }); const persisted = await store.readJob(fixture.workspace, reserved.job.id);
157+
assert.equal(persisted.status, stopSucceeds ? 'failed' : 'running');
158+
if (!stopSucceeds) {
159+
assert.match(persisted.lastCancelError, /fixture stop failed/);
160+
await assert.rejects(store.reserveJob({ workspace: fixture.workspace, ownerSessionId: 'owner', ownerTurnId: 'later', command: 'rescue', readOnly: false, permissionSnapshot: { permissionMode: 'workspace-write' } }), { code: 'WRITABLE_JOB_EXISTS' });
161+
}
162+
}
163+
});
164+
134165
test('foreground and background workers persist their exact lease before discovery', async () => {
135166
for (const execution of ['foreground', 'background']) {
136167
const fixture = await context(); const store = createStateStore({ dataRoot: fixture.dataRoot }); let observed;

0 commit comments

Comments
 (0)