vxcontrol/pentagi · error

failed to parse stop_flow args: %w

Error message

failed to parse stop_flow args: %w

What it means

The stop_flow tool could not unmarshal its raw JSON arguments into StopFlowAction. The decode error is wrapped so the original json error remains inspectable. It guards the boundary between the agent tool-call protocol and the typed action struct.

Source

Thrown at backend/pkg/tools/flow_manager.go:620

			"Call %s with detail='summary' to see the final status and results.",
		GetFlowStatusToolName), nil
}

// stopFlowTool implements stop_flow.
type stopFlowTool struct {
	flowID  int64
	db      database.Querier
	handler func(ctx context.Context, reason string) error
}

func NewStopFlowTool(flowID int64, db database.Querier, handler func(ctx context.Context, reason string) error) *stopFlowTool {
	return &stopFlowTool{flowID: flowID, db: db, handler: handler}
}

func (t *stopFlowTool) Handle(ctx context.Context, name string, args json.RawMessage) (string, error) {
	var action StopFlowAction
	if err := json.Unmarshal(args, &action); err != nil {
		return "", fmt.Errorf("failed to parse stop_flow args: %w", err)
	}

	tasks, err := t.db.GetFlowTasks(ctx, t.flowID)
	if err != nil {
		return "", fmt.Errorf("failed to check flow status: %w", err)
	}

	isRunning := false
	for _, task := range tasks {
		if task.Status == database.TaskStatusRunning {
			isRunning = true
			break
		}
	}

	if !isRunning {
		return "No running task found — the flow is already in 'waiting' state and ready to accept input.", nil
	}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Send stop_flow args as a valid JSON object with correct types, e.g. {"reason": "user requested stop"}.
  2. Check the wrapped inner error to distinguish SyntaxError (bad JSON) from UnmarshalTypeError (bad field type).
  3. Avoid double-encoding: pass an object, not a JSON string of an object.
  4. Retry the tool call if the args were generated by an LLM — regenerated JSON is often valid.

Example fix

// before (double-encoded)
args := `"{\"reason\":\"done\"}"`
// after
args := `{"reason":"done"}`
Defensive patterns

Strategy: validation

Validate before calling

const args = { reason: 'user requested stop' };
if (typeof args.reason !== 'string') throw new TypeError('reason must be a string');
JSON.parse(JSON.stringify(args)); // round-trip validate

Type guard

function isStopFlowAction(v: unknown): v is { reason?: string } {
  return typeof v === 'object' && v !== null &&
    (!('reason' in v) || typeof (v as any).reason === 'string');
}

Try / catch

try {
  await tool.call('stop_flow', args);
} catch (err) {
  if (String(err).includes('failed to parse stop_flow args')) {
    // rebuild args as a valid JSON object and retry
  }
}

Prevention

When it happens

Trigger: Calling stop_flow with malformed JSON, a non-object payload, or wrong-typed fields — e.g. {"reason": 123} (number instead of string) or truncated JSON produced by the LLM or client.

Common situations: LLM emits invalid JSON for the tool call; a caller stringifies args incorrectly (double-encoded JSON like "{\"reason\":\"x\"}"); reason field given a non-string value.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/7e04cfb8daa6bc2a. Report an issue: GitHub.