vxcontrol/pentagi · error

failed to get flow subtasks

Error message

failed to get flow subtasks: %w

What it means

This error is thrown by the get_flow_status tool's buildSummary (backend/pkg/tools/flow_manager.go:99) when the database query GetFlowSubtasks fails while assembling the flow summary. It wraps the underlying DB error with %w so the root cause (connection failure, timeout, SQL error) is preserved. It means the tool could not read the subtask list for the tool's flowID.

Solutions

  1. Check PostgreSQL connectivity and container health (docker compose ps, pg_isready) and restart the database if it is down.
  2. Inspect the wrapped cause with errors.Unwrap / logs — fix the specific DB error (timeout, pool limits, bad credentials in .env).
  3. Verify goose migrations ran to completion (backend/migrations/sql) so the subtasks table/schema matches the SQLC queries.
  4. Increase DB pool size / statement timeout, or retry the tool call once the database is reachable.

Example fix

// before
subtasks, err := t.db.GetFlowSubtasks(ctx, t.flowID)
if err != nil {
	return "", fmt.Errorf("failed to get flow subtasks: %w", err)
}
// after: retry transient DB failures once before failing the tool call
subtasks, err := t.db.GetFlowSubtasks(ctx, t.flowID)
if err != nil && isTransientDBError(err) {
	time.Sleep(500 * time.Millisecond)
	subtasks, err = t.db.GetFlowSubtasks(ctx, t.flowID)
}
if err != nil {
	return "", fmt.Errorf("failed to get flow subtasks: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// before relying on the tool, confirm DB reachability from the app host
rows, err := db.QueryContext(ctx, "SELECT 1 FROM subtasks LIMIT 1")
if err != nil {
	return fmt.Errorf("database not ready for get_flow_status: %w", err)
}

Try / catch

// Go: classify the wrapped cause before reacting
result, err := tool.Handle(ctx, "get_flow_status", args)
if err != nil {
	if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
		return retryWithBackoff(ctx, err)
	}
	var pgErr *pgconn.PgError
	if errors.As(err, &pgErr) && pgErr.Code == "53300" { // too_many_connections
		return backoffAndRetry(ctx)
	}
	return fmt.Errorf("get_flow_status failed: %w", err)
}

Prevention

When it happens

Trigger: Calling the get_flow_status tool with detail='summary' (Handle -> buildSummary) while t.db.GetFlowSubtasks(ctx, t.flowID) returns an error — e.g. PostgreSQL unreachable, statement timeout, context cancellation, or a schema mismatch after a failed migration.

Common situations: Database container down or restarting during heavy agent runs; connection-pool exhaustion under many concurrent flows; pgvector extension or migration version mismatch; context deadline exceeded because the caller's LLM request timeout is shorter than the DB latency.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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

Appendix: source

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

		return t.buildSubtasksList(ctx, action.TaskID.PtrInt64(), verbose)
	case FlowStatusDetailRunning:
		return t.buildRunningInfo(ctx, verbose)
	case FlowStatusDetailPlanned:
		return t.buildPlannedList(ctx, action.TaskID.PtrInt64(), verbose)
	default:
		return "", fmt.Errorf("unknown detail level %q; use one of: summary, tasks, subtasks, running, planned", action.Detail)
	}
}

func (t *flowStatusTool) buildSummary(ctx context.Context, verbose bool) (string, error) {
	tasks, err := t.db.GetFlowTasks(ctx, t.flowID)
	if err != nil {
		return "", fmt.Errorf("failed to get flow tasks: %w", err)
	}

	subtasks, err := t.db.GetFlowSubtasks(ctx, t.flowID)
	if err != nil {
		return "", fmt.Errorf("failed to get flow subtasks: %w", err)
	}

	taskCounts := map[string]int{"created": 0, "running": 0, "waiting": 0, "finished": 0, "failed": 0}
	var activeTask *database.Task
	for i, task := range tasks {
		taskCounts[string(task.Status)]++
		if task.Status == database.TaskStatusRunning || task.Status == database.TaskStatusWaiting {
			activeTask = &tasks[i]
		}
	}

	stCounts := map[string]int{"created": 0, "running": 0, "waiting": 0, "finished": 0, "failed": 0}
	var activeST *database.Subtask
	for i, st := range subtasks {
		stCounts[string(st.Status)]++
		if st.Status == database.SubtaskStatusRunning || st.Status == database.SubtaskStatusWaiting {
			activeST = &subtasks[i]
		}

View on GitHub (pinned to ea665308ba)