Skip to content

Commit fde0d0c

Browse files
committed
fix: defer approval index cleanup until after successful resume
Addresses Codex review feedback on PR openclaw#48. Previously, the approval ID index was deleted before executing the resumed pipeline/workflow. If execution failed with a transient error, the state was still resumable by --token but the --id alias was already gone. Now: index cleanup happens after successful completion or explicit denial. On runtime_error, the index is preserved so the user can retry with the same --id.
1 parent 672faf7 commit fde0d0c

File tree

3 files changed

+297
-14
lines changed

3 files changed

+297
-14
lines changed

package-lock.json

Lines changed: 268 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -339,15 +339,17 @@ async function handleResume({ argv, registry }) {
339339
return;
340340
}
341341

342-
// Clean up approval ID index after use
343-
if (resolvedApprovalId) {
344-
await deleteApprovalId({ env: process.env, approvalId: resolvedApprovalId });
345-
} else if (payload.stateKey) {
346-
// --token path: clean up any orphaned approval index for this stateKey
347-
await cleanupApprovalIndexByStateKey({ env: process.env, stateKey: payload.stateKey });
348-
}
342+
// Helper: clean up approval ID index after successful use
343+
const cleanupIndex = async () => {
344+
if (resolvedApprovalId) {
345+
await deleteApprovalId({ env: process.env, approvalId: resolvedApprovalId });
346+
} else if (payload.stateKey) {
347+
await cleanupApprovalIndexByStateKey({ env: process.env, stateKey: payload.stateKey });
348+
}
349+
};
349350

350351
if (!approved) {
352+
await cleanupIndex();
351353
if (payload.kind === 'workflow-file' && payload.stateKey) {
352354
await deleteStateJson({ env: process.env, key: payload.stateKey });
353355
}
@@ -384,9 +386,11 @@ async function handleResume({ argv, registry }) {
384386
return;
385387
}
386388

389+
await cleanupIndex();
387390
writeToolEnvelope({ ok: true, status: 'ok', output: output.output, requiresApproval: null });
388391
return;
389392
} catch (err) {
393+
// Don't clean up index on error — allow retry by --id
390394
writeToolEnvelope({ ok: false, error: { type: 'runtime_error', message: err?.message ?? String(err) } });
391395
process.exitCode = 1;
392396
return;
@@ -428,6 +432,7 @@ async function handleResume({ argv, registry }) {
428432
prompt: approval.prompt,
429433
createdAt: new Date().toISOString(),
430434
});
435+
await cleanupIndex();
431436
await deleteStateJson({ env: process.env, key: previousStateKey });
432437

433438
const nextAid = generateApprovalId();
@@ -449,9 +454,11 @@ async function handleResume({ argv, registry }) {
449454
return;
450455
}
451456

457+
await cleanupIndex();
452458
await deleteStateJson({ env: process.env, key: previousStateKey });
453459
writeToolEnvelope({ ok: true, status: 'ok', output: output.items, requiresApproval: null });
454460
} catch (err) {
461+
// Don't clean up index on error — allow retry by --id
455462
writeToolEnvelope({ ok: false, error: { type: 'runtime_error', message: err?.message ?? String(err) } });
456463
process.exitCode = 1;
457464
}

src/core/tool_runtime.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,17 @@ export async function resumeToolRequest({
195195
return errorEnvelope('parse_error', err?.message ?? String(err));
196196
}
197197

198-
// Clean up approval ID index after use
199-
if (resolvedApprovalId) {
200-
await deleteApprovalId({ env: runtime.env, approvalId: resolvedApprovalId });
201-
} else if (payload?.stateKey) {
202-
// --token path: clean up any orphaned approval index for this state key
203-
await cleanupApprovalIndexByStateKey({ env: runtime.env, stateKey: payload.stateKey });
204-
}
198+
// Helper: clean up approval ID index after successful use
199+
const cleanupIndex = async () => {
200+
if (resolvedApprovalId) {
201+
await deleteApprovalId({ env: runtime.env, approvalId: resolvedApprovalId });
202+
} else if (payload?.stateKey) {
203+
await cleanupApprovalIndexByStateKey({ env: runtime.env, stateKey: payload.stateKey });
204+
}
205+
};
205206

206207
if (!approved) {
208+
await cleanupIndex();
207209
if (payload.kind === 'workflow-file' && payload.stateKey) {
208210
await deleteStateJson({ env: runtime.env, key: payload.stateKey });
209211
}
@@ -223,13 +225,16 @@ export async function resumeToolRequest({
223225
});
224226

225227
if (output.status === 'needs_approval') {
228+
// Don't clean up index — next gate will issue a new approvalId
226229
return okEnvelope('needs_approval', [], output.requiresApproval ?? null);
227230
}
231+
await cleanupIndex();
228232
if (output.status === 'cancelled') {
229233
return okEnvelope('cancelled', [], null);
230234
}
231235
return okEnvelope('ok', output.output, null);
232236
} catch (err: any) {
237+
// Don't clean up index on error — allow retry by --id
233238
return errorEnvelope('runtime_error', err?.message ?? String(err));
234239
}
235240
}
@@ -273,6 +278,7 @@ export async function resumeToolRequest({
273278
createdAt: new Date().toISOString(),
274279
});
275280
await writeApprovalIndex({ env: runtime.env, stateKey: nextStateKey, approvalId: nextAid });
281+
await cleanupIndex();
276282
await deleteStateJson({ env: runtime.env, key: payload.stateKey });
277283

278284
const resumeToken = encodeToken({
@@ -289,9 +295,11 @@ export async function resumeToolRequest({
289295
});
290296
}
291297

298+
await cleanupIndex();
292299
await deleteStateJson({ env: runtime.env, key: payload.stateKey });
293300
return okEnvelope('ok', output.items, null);
294301
} catch (err: any) {
302+
// Don't clean up index on error — allow retry by --id
295303
return errorEnvelope('runtime_error', err?.message ?? String(err));
296304
}
297305
}

0 commit comments

Comments
 (0)