{"record":{"id":"755c1bed327f1459","repo":"vxcontrol/pentagi","slug":"failed-to-get-subtask-msg-logs-w","errorCode":null,"errorMessage":"failed to get subtask msg logs: %w","messagePattern":"failed to get subtask msg logs: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/flow_manager.go","lineNumber":343,"sourceCode":"\t\tif verbose && st.Context != \"\" {\n\t\t\texecContext, err := t.getDescriptionText(ctx, st.Context)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to get subtask context: %w\", err)\n\t\t\t}\n\t\t\tfmt.Fprintf(sb, \"\\nExecution context:\\n%s\\n\", execContext)\n\t\t}\n\t\tif st.Status == database.SubtaskStatusWaiting {\n\t\t\tfmt.Fprintf(sb, \"\\nNote: subtask is waiting for user input (ask state).\\n\")\n\t\t\tfmt.Fprintf(sb, \"Use %s to provide the answer and resume execution.\\n\", SubmitFlowInputToolName)\n\t\t}\n\n\t\tmsgLimit := msgLogLimitNormal\n\t\tif verbose {\n\t\t\tmsgLimit = msgLogLimitVerbose\n\t\t}\n\t\tmsgLogs, err := t.appendSubtaskMsgLogs(ctx, st.ID, msgLimit)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to get subtask msg logs: %w\", err)\n\t\t}\n\t\tsb.WriteString(msgLogs)\n\n\t\trunningInfo := sb.String()\n\t\tif t.summarizer != nil && len(runningInfo) > runningInfoLimit {\n\t\t\trunningInfo, err = t.summarizer(ctx, truncateText(runningInfo, summarizationLimit))\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to summarize running info: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn runningInfo, nil\n\t}\n\n\treturn \"No running or waiting subtask found. Flow is idle (waiting for next input).\", nil\n}\n\nfunc (t *flowStatusTool) buildPlannedList(ctx context.Context, taskID *int64, verbose bool) (string, error) {","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/flow_manager.go#L325-L361","documentation":"Returned by flowStatusTool.buildRunningInfo (backend/pkg/tools/flow_manager.go:343) when fetching the most recent agent message logs for the active subtask. appendSubtaskMsgLogs queries the database via t.db.GetSubtaskMsgLogs(ctx, subtaskID); any database error (connection failure, deadlock, cancelled context, missing table) is wrapped here. Unlike errors 755-758, the root cause is the database layer, not the LLM summarizer — the internal summarizer failure inside appendSubtaskMsgLogs produces a different message ('failed to summarize msg logs').","triggerScenarios":"flow_status tool invoked while a subtask is running/waiting and the PostgreSQL query GetSubtaskMsgLogs fails: DB connection dropped/pooled out, PostgreSQL restart, statement timeout, ctx cancelled mid-query, or schema drift (missing msg_logs table if migrations did not run).","commonSituations":"Database outage or failover during a flow; connection pool exhaustion under heavy agent concurrency; goose migrations not applied so GetSubtaskMsgLogs references a missing table/column; network partition between the backend container and the postgres container in Docker Compose.","solutions":["Check the wrapped %w cause and backend logs for the concrete PostgreSQL error (connection refused, timeout, undefined table).","Verify PostgreSQL is reachable and healthy (docker compose ps, pg_isready) and that backend/env DB credentials are correct.","Run pending goose migrations (backend/migrations/sql) to ensure the msg log tables exist.","Retry the flow_status call after the DB recovers — transient connection errors self-heal once pooling reconnects.","Increase connection pool limits / statement timeouts if pool exhaustion under load is the cause."],"exampleFix":"// before: no retry on transient DB failure\nlogs, err := t.db.GetSubtaskMsgLogs(ctx, nullID)\nif err != nil {\n\treturn \"\", fmt.Errorf(\"failed to get subtask msg logs: %w\", err)\n}\n\n// after: bounded retry for transient connection errors\nvar logs []database.SubtaskMsgLog\nerr = retry(ctx, 3, 500*time.Millisecond, func() error {\n\tvar e error\n\tlogs, e = t.db.GetSubtaskMsgLogs(ctx, nullID)\n\treturn e\n})\nif err != nil {\n\treturn \"\", fmt.Errorf(\"failed to get subtask msg logs: %w\", err)\n}","handlingStrategy":"retry","validationCode":"// before invoking the tool, confirm the database accepts queries\nif err := db.PingContext(ctx); err != nil {\n\treturn fmt.Errorf(\"database unavailable: %w\", err)\n}\n// ensure migrations applied\ndown, _ := migrationsPending(ctx)\nif down {\n\treturn fmt.Errorf(\"pending database migrations; run goose up\")\n}","typeGuard":"func isTransientDBError(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\terr = errors.Unwrap(err)\n\tvar pgErr *pgconn.PgError\n\tif errors.As(err, &pgErr) {\n\t\treturn pgcode.IsConnectionException(pgErr.Code) || pgErr.Code == pgcode.SerializationFailure\n\t}\n\treturn errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.EOF)\n}","tryCatchPattern":"logs, err := appendSubtaskMsgLogs(ctx, st.ID, limit)\nif err != nil {\n\tif isTransientDBError(err) {\n\t\t// retry after backoff — pooled connections usually recover\n\t\tlogs, err = retryWithBackoff(ctx, 3, 500*time.Millisecond, func() (string, error) {\n\t\t\treturn appendSubtaskMsgLogs(ctx, st.ID, limit)\n\t\t})\n\t}\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get subtask msg logs: %w\", err)\n\t}\n}","preventionTips":["Run goose migrations at startup so msg log tables always exist.","Set sensible pool size and statement timeouts (pgxpool defaults) for agent concurrency.","Add health checks (pg_isready) in Docker Compose with depends_on conditions.","Alert on connection pool saturation and PostgreSQL failover events.","Wrap DB queries with bounded retry for transient connection/serialization errors."],"tags":["go","database","postgresql","flow-status"],"backgroundTag":"database-query-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}