{"record":{"id":"1dd456bb47ca3b92","repo":"micro/go-micro","slug":"agent-checkpointed-run-is-terminal-with-status","errorCode":null,"errorMessage":"agent: checkpointed run is terminal with status ","messagePattern":"agent: checkpointed run is terminal with status ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/stream.go","lineNumber":159,"sourceCode":"\tif a.opts.Checkpoint == nil {\n\t\treturn nil, errors.New(\"agent: ResumeStreamAsk requires a checkpoint\")\n\t}\n\trun, ok, err := a.opts.Checkpoint.Load(ctx, runID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !ok {\n\t\treturn nil, errors.New(\"agent: checkpointed run not found\")\n\t}\n\tif run.Status == \"done\" {\n\t\tvar resp Response\n\t\tif err := json.Unmarshal(run.State.Data, &resp); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &resp, nil\n\t}\n\tif terminalAgentRunStatus(run.Status) {\n\t\treturn nil, errors.New(\"agent: checkpointed run is terminal with status \" + run.Status)\n\t}\n\n\ta.mu.Lock()\n\tdefer a.mu.Unlock()\n\tif a.tools == nil {\n\t\ta.tools = ai.NewTools(a.opts.Registry, ai.ToolClient(a.opts.Client))\n\t}\n\tbase := a.toolHandler()\n\thandler := func(ctx context.Context, call ai.ToolCall) ai.ToolResult {\n\t\t_ = sendStreamEvent(ctx, events, &StreamEvent{Type: StreamEventToolStart, ToolCall: call})\n\t\tresult := base(ctx, call)\n\t\t_ = sendStreamEvent(ctx, events, &StreamEvent{Type: StreamEventToolEnd, ToolCall: call, Result: result})\n\t\treturn result\n\t}\n\ta.setupWithToolHandler(handler)\n\tdefer a.setupWithToolHandler(nil)\n\tif run.Status == \"paused\" {\n\t\tif run.State.Stage == agentInputStep {","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/agent/stream.go#L141-L177","documentation":"The checkpointed run is in a terminal state (per terminalAgentRunStatus), so it cannot be resumed. Terminal runs have finished (successfully, failed, or otherwise permanently stopped) and no further work can be streamed.","triggerScenarios":"Calling ResumeStreamAsk/ResumeStream on a run whose stored Status is a terminal value other than \"done\" (e.g. failed/canceled status), after that status was persisted by a prior run.","commonSituations":"Retrying a resume after the run already failed; double-resuming a canceled run; race where another process terminated the run between status check and resume.","solutions":["Inspect run.Status via Checkpoint.Load before resuming and only resume non-terminal runs","Treat terminal failed runs as unrecoverable: start a new run instead","If the run completed, load and return the persisted Response rather than resuming"],"exampleFix":"// before\nstream, err := agent.ResumeStreamAsk(ctx, ag, runID)\n// after\nrun, ok, _ := store.Load(ctx, runID)\nif ok && run.Status == \"failed\" {\n    return startNewRun(ctx, ag) // don't resume a terminal run\n}\nstream, err := agent.ResumeStreamAsk(ctx, ag, runID)","handlingStrategy":"validation","validationCode":"run, ok, _ := store.Load(ctx, runID)\nif ok && run.Status != \"running\" && run.Status != \"paused\" {\n    return fmt.Errorf(\"run %s is terminal (%s); cannot resume\", runID, run.Status)\n}","typeGuard":null,"tryCatchPattern":"stream, err := agent.ResumeStreamAsk(ctx, ag, runID)\nif err != nil && strings.Contains(err.Error(), \"terminal with status\") {\n    return fmt.Errorf(\"run already finished; fetch result or start a new run\")\n}","preventionTips":["Check run.Status before resuming; only resume non-terminal runs","Handle the \"done\" case by loading the persisted Response instead of resuming","Guard against double-resume with per-run locks or status transitions in your orchestration"],"tags":["go","agent","checkpoint","state-machine","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}