@@ -815,9 +815,24 @@ export async function runInvestigation({ task, max_steps = 6, resume_run_id }) {
815815 // before this field existed won't have it; fall back to whatever the
816816 // caller passed (may be undefined) rather than erroring.
817817 effectiveTask = checkpoint . task || task ;
818+ } else if ( resume_run_id ) {
819+ // A resume WAS requested but its checkpoint didn't load -- expired past
820+ // the 1-hour TTL, Redis unavailable (checkpoint.js is deliberately
821+ // fail-open, see its header), or an invalid/typo'd runId. This must
822+ // NEVER be silently treated as "no resume was requested" and fall
823+ // through to a fresh run: that previously produced a conversation
824+ // seeded with `Task: undefined` (task is genuinely ignored on a live
825+ // resume, so callers legitimately omit it), and the model burned several
826+ // steps hunting blind for context instead of investigating (found via
827+ // the 2026-07-26 checkpoint-miss test). Fail loudly and distinctly
828+ // instead, so the caller can tell "your resume target is gone" apart
829+ // from any other failure and decide whether to retry with a task.
830+ throw new Error (
831+ `resume_run_id "${ resume_run_id } " has no live checkpoint -- it may have expired (1 hour TTL), Redis may be unavailable, or the id may be wrong. ` +
832+ `There is no saved task to resume from. Start a new investigation by calling again with a task and no resume_run_id.`
833+ ) ;
818834 } else {
819- // Either no resume_run_id was given, or the checkpoint had already
820- // expired/wasn't found -- start a fresh run either way. Requires a real
835+ // No resume_run_id was given -- start a fresh run. Requires a real
821836 // `task` (the caller-facing tool in tools.js already guards against a
822837 // missing task on a non-resumable call, so `task` is trustworthy here).
823838 runId = randomUUID ( ) ;
0 commit comments