fix(cli): race condition in thread rm + flaky test (#265)

Two fixes:
1. cmdThreadRemove: always call both removeThreadEntry and
   removeThreadHistoryEntries regardless of resolved source,
   preventing race where thread moves from active to history
   between resolve and delete.
2. Test: add waitUntilRunningFileAbsent before thread show/rm,
   matching the pattern used by adjacent test cases.

Verified 5x consecutive runs with 0 failures.

Closes #265
This commit is contained in:
2026-05-14 13:17:04 +00:00
parent 71ccf8d03c
commit 942ff4b1a4
2 changed files with 10 additions and 7 deletions
@@ -180,6 +180,9 @@ describe("cli thread commands", () => {
} }
expect(threads.value.some((l) => l.includes(threadId))).toBe(true); expect(threads.value.some((l) => l.includes(threadId))).toBe(true);
const runningPath = join(storageRoot, "logs", added.value.hash, `${threadId}.running`);
await waitUntilRunningFileAbsent(runningPath, 120);
const shown = await cmdThreadShow(storageRoot, threadId); const shown = await cmdThreadShow(storageRoot, threadId);
expect(shown.ok).toBe(true); expect(shown.ok).toBe(true);
if (!shown.ok) { if (!shown.ok) {
@@ -18,13 +18,13 @@ export async function cmdThreadRemove(
return err(`thread not found: ${threadId}`); return err(`thread not found: ${threadId}`);
} }
if (resolved.source === "active") { // Always clear both stores: between resolve and delete the worker may finish and
await removeThreadEntry(resolved.bundleDir, threadId); // move the thread from threads.json into history; branching only on resolved.source
} else { // would skip history removal and leave a dangling row.
const hist = await removeThreadHistoryEntries(resolved.bundleDir, threadId); await removeThreadEntry(resolved.bundleDir, threadId);
if (!hist.ok) { const hist = await removeThreadHistoryEntries(resolved.bundleDir, threadId);
return hist; if (!hist.ok) {
} return hist;
} }
const infoPath = join(storageRoot, "logs", resolved.bundleHash, `${threadId}.info.jsonl`); const infoPath = join(storageRoot, "logs", resolved.bundleHash, `${threadId}.info.jsonl`);