{"record":{"id":"a63825fa4b0c774e","repo":"vxcontrol/pentagi","slug":"failed-to-create-tool-call-log-w","errorCode":null,"errorMessage":"failed to create tool call log: %w","messagePattern":"failed to create tool call log: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/tclog.go","lineNumber":68,"sourceCode":"\tname string,\n\targs json.RawMessage,\n\ttaskID *int64,\n\tsubtaskID *int64,\n) (int64, error) {\n\tw.mx.Lock()\n\tdefer w.mx.Unlock()\n\n\ttc, err := w.db.CreateToolcall(ctx, database.CreateToolcallParams{\n\t\tCallID:    callID,\n\t\tStatus:    database.ToolcallStatusRunning,\n\t\tName:      name,\n\t\tArgs:      args,\n\t\tFlowID:    w.flowID,\n\t\tTaskID:    database.Int64ToNullInt64(taskID),\n\t\tSubtaskID: database.Int64ToNullInt64(subtaskID),\n\t})\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to create tool call log: %w\", err)\n\t}\n\n\tw.pub.ToolCallLogAdded(ctx, tc)\n\n\treturn tc.ID, nil\n}\n\nfunc (w *flowToolCallLogWorker) UpdateLogSuccess(\n\tctx context.Context,\n\tid int64,\n\tresult string,\n\tdurationSeconds float64,\n) error {\n\ttc, err := w.db.UpdateToolcallFinishedResult(ctx, database.UpdateToolcallFinishedResultParams{\n\t\tResult:          result,\n\t\tDurationSeconds: durationSeconds,\n\t\tID:              id,\n\t})","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/tclog.go#L50-L86","documentation":"PutLog persists a tool call record via db.CreateToolcall before executing tool logic. This error wraps any PostgreSQL failure during that insert (connection loss, FK violation on flow_id/task_id/subtask_id, context cancellation, constraint violation). It is a wrapper, so the root cause is always in the %w-wrapped error.","triggerScenarios":"Calling PutLog with a subtaskID whose row was already deleted (FK violation), a taskID from another flow, an invalid JSON args blob rejected by a jsonb column, or while the DB connection is down/canceled ctx.","commonSituations":"Agents racing subtask cleanup so the subtask row disappears mid-run; transient DB restarts during long flows; passing task IDs from a stale/foreign flow after flow recreation.","solutions":["Inspect the wrapped cause with errors.Unwrap / %v to distinguish FK violation vs connection error","Verify taskID/subtaskID belong to the same flow and still exist before calling PutLog","Check PostgreSQL logs for the exact constraint (e.g. toolcalls_subtask_id_fkey)","Ensure pgvector/Postgres container is healthy (docker compose ps, connection pool limits)","Retry on transient errors (pq: connection reset, driver.ErrBadConn) only; do not retry FK violations"],"exampleFix":"// before\nid, err := worker.PutLog(ctx, callID, name, args, &staleTaskID, &staleSubtaskID)\n// after\nvar taskID, subtaskID int64 = staleTaskID, staleSubtaskID\nif !taskExists(ctx, db, taskID) || !subtaskInFlow(ctx, db, subtaskID, flowID) {\n    taskID, subtaskID = 0, 0 // pass nil instead of dangling FKs\n}\nid, err := worker.PutLog(ctx, callID, name, args, maybePtr(taskID), maybePtr(subtaskID))","handlingStrategy":"validation","validationCode":"func canLog(ctx context.Context, q database.Querier, flowID int64, taskID, subtaskID *int64) error {\n    if ctx.Err() != nil { return ctx.Err() }\n    if taskID != nil {\n        if _, err := q.GetFlowTask(ctx, flowID, *taskID); err != nil { return fmt.Errorf(\"task %d not in flow %d: %w\", *taskID, flowID, err) }\n    }\n    if subtaskID != nil {\n        if _, err := q.GetSubtask(ctx, *subtaskID); err != nil { return fmt.Errorf(\"subtask %d missing: %w\", *subtaskID, err) }\n    }\n    return nil\n}","typeGuard":"func isForeignKeyErr(err error) bool {\n    var pgErr *pgconn.PgError\n    return errors.As(err, &pgErr) && pgErr.Code == \"23503\"\n}","tryCatchPattern":"id, err := worker.PutLog(ctx, callID, name, args, taskID, subtaskID)\nif err != nil {\n    switch {\n    case isForeignKeyErr(err):\n        log.Warn().Err(err).Msg(\"dangling task/subtask reference; logging without FKs\")\n        id, err = worker.PutLog(ctx, callID, name, args, nil, nil)\n    case errors.Is(err, context.Canceled):\n        return err\n    default:\n        return fmt.Errorf(\"putlog: %w\", err)\n    }\n}","preventionTips":["Always take taskID/subtaskID from the same agent iteration that owns the flow","Never reuse IDs from a previous flow run","Check ctx.Err() before long DB writes","Alert on Postgres constraint 23503 occurrences to find lifecycle races","Monitor DB connection health during long agent runs"],"tags":["database","postgresql","foreign-key"],"backgroundTag":"foreign-key-violation","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}