micro/go-micro · error

agent run %s is terminal with status %q

Error message

agent run %s is terminal with status %q

What it means

agent.Resume refuses to continue runs already in a terminal state (e.g. "failed", "canceled") other than "done". Terminal runs have concluded and cannot be re-executed; the error includes the actual status so the caller can decide what to do. Only non-terminal (running/paused in resumable stages) or done runs are resumable.

Source

Thrown at agent/checkpoint.go:104

	if !ok {
		return nil, fmt.Errorf("agent run %s not found", runID)
	}
	if run.Status == "paused" {
		if run.State.Stage == agentInputStep {
			return nil, fmt.Errorf("agent run %s is input-required; resume with ResumeInput", runID)
		}
		run.Status = "running"
		run.State.Stage = agentAskStep
	}
	if run.Status == "done" {
		var resp Response
		if err := json.Unmarshal(run.State.Data, &resp); err != nil {
			return nil, fmt.Errorf("agent run %s response decode: %w", runID, err)
		}
		return &resp, nil
	}
	if terminalAgentRunStatus(run.Status) {
		return nil, fmt.Errorf("agent run %s is terminal with status %q", runID, run.Status)
	}
	message := string(run.State.Data)
	parentID := run.ParentID
	a.mu.Lock()
	defer a.mu.Unlock()
	if a.model == nil {
		a.setup()
	}
	return a.askLocked(ctx, run.ID, message, parentID, &run, false)
}

// ResumeInput resumes a checkpointed agent run that paused via the built-in
// request_input tool. The supplied input is appended to the original request so
// the same run can continue with durable checkpoint and completed tool history.
func ResumeInput(ctx context.Context, ag Agent, runID, input string) (*Response, error) {
	a, ok := ag.(*agentImpl)
	if !ok {
		return nil, fmt.Errorf("agent resume input: unsupported agent implementation %T", ag)

View on GitHub (pinned to 24529f1404)

Solutions

  1. Do not resume; treat the run as finished and start a new agent run with the original or amended message.
  2. Check the run's status in the checkpoint store before calling Resume and skip terminal runs in retry loops.
  3. If the run failed transiently, create a fresh run (new runID) rather than resuming the failed one.

Example fix

// before
resp, err := agent.Resume(ctx, ag, runID) // status: canceled
// after
run, ok, _ := ckpt.Load(ctx, runID)
if ok && isTerminal(run.Status) {
    runID = "" // start fresh
}
resp, err = ag.Run(ctx, message)
Defensive patterns

Strategy: validation

Validate before calling

run, ok, _ := ckpt.Load(ctx, runID)
if ok && (run.Status == "failed" || run.Status == "canceled") {
    return fmt.Errorf("run %s already terminal; start a new run", runID)
}

Type guard

func isResumable(r flow.Run) bool {
    switch r.Status {
    case "running", "paused", "":
        return r.Status != "paused" || r.State.Stage != "input-required"
    default:
        return false
    }
}

Try / catch

resp, err := agent.Resume(ctx, ag, runID)
if err != nil && strings.Contains(err.Error(), "is terminal") {
    // mark job permanently failed; do not retry
}

Prevention

When it happens

Trigger: Calling agent.Resume with a runID whose checkpointed run has a terminal status such as "failed" or "canceled", as checked by terminalAgentRunStatus.

Common situations: Replaying old run IDs from an incident or migration; a scheduler retrying resumes for runs that were canceled by another component; double-processing in queues where the run already failed permanently.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/e0417bb638bf43bb. Report an issue: GitHub.