From 4fd6317aa48d3d383b9273af00a920f50a8e5e75 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 10 Jul 2026 20:44:44 -0700 Subject: [PATCH] fix: accurate error when updateStep can't read prompt-derived steps (greptile P1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a task's steps live only in an unreadable PROMPT.md, updateStep still can't resolve step 0 — but it now throws an error naming PROMPT.md as the cause instead of a misleading "task has 0 steps". The operation genuinely cannot succeed (no step data), so this is accurate reporting, not a silent success. The reachable reset path (which derives its loop from getTask's steps) remains unaffected. Adds a test asserting the PROMPT.md-attributed error. Co-Authored-By: Claude Opus 4.8 --- .../task-detail-prompt-resilience.test.ts | 5 +++++ packages/core/src/store.ts | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/core/src/__tests__/task-detail-prompt-resilience.test.ts b/packages/core/src/__tests__/task-detail-prompt-resilience.test.ts index 2d685a89ca..750d65ffb3 100644 --- a/packages/core/src/__tests__/task-detail-prompt-resilience.test.ts +++ b/packages/core/src/__tests__/task-detail-prompt-resilience.test.ts @@ -83,6 +83,11 @@ describe("getTask PROMPT.md read resilience (task-write-API 500 regression)", () await expect(store.moveTask(task.id, "in-progress")).resolves.toBeTruthy(); await expect(store.moveTask(task.id, "todo")).resolves.toBeTruthy(); + // Directly updating a step whose definition lives only in the unreadable + // PROMPT.md genuinely cannot succeed, but the error must name the real + // cause (PROMPT.md) rather than a misleading "task has 0 steps". + await expect(store.updateStep(task.id, 0, "in-progress")).rejects.toThrow(/PROMPT\.md/); + await expect(store.archiveTask(task.id)).resolves.toBeTruthy(); const archived = await store.getTask(task.id); expect(archived.column).toBe("archived"); diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index a8a9530f72..82c0c95862 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -9965,11 +9965,17 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS} // FNXC:TaskDetailPromptResilience 2026-07-10-15:00: step auto-init is // best-effort — an unreadable PROMPT.md must not fail updateStep (on the // reported reset path); proceed with the persisted (empty) steps. + let promptStepsUnavailable: string | undefined; if (task.steps.length === 0 && !graphSource) { try { task.steps = await this.parseStepsFromPrompt(id); } catch (err) { - storeLog.warn(`[task-detail] failed to auto-init steps from PROMPT.md for ${id}: ${getErrorMessage(err)}`); + // Remember WHY steps couldn't be resolved so the range check below + // attributes the failure to the unreadable PROMPT.md rather than a + // misleading "0 steps". A step defined only in an unreadable PROMPT.md + // genuinely cannot be updated — but the error should say so. + promptStepsUnavailable = getErrorMessage(err); + storeLog.warn(`[task-detail] failed to auto-init steps from PROMPT.md for ${id}: ${promptStepsUnavailable}`); } } @@ -9979,6 +9985,14 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS} } if (stepIndex < 0 || stepIndex >= task.steps.length) { + // FNXC:TaskDetailPromptResilience 2026-07-10-16:30: when the range failure + // is caused by an unreadable PROMPT.md (not a genuinely stepless task), + // surface the real cause instead of a confusing "task has 0 steps". + if (promptStepsUnavailable !== undefined && task.steps.length === 0) { + throw new Error( + `Cannot update step ${stepIndex} for ${id}: its steps are defined in PROMPT.md, which could not be read (${promptStepsUnavailable}).`, + ); + } throw new Error( `Step ${stepIndex} out of range (task has ${task.steps.length} steps)`, );