{"record":{"id":"f76cb35762182903","repo":"vxcontrol/pentagi","slug":"failed-to-create-termlog-w","errorCode":null,"errorMessage":"failed to create termlog: %w","messagePattern":"failed to create termlog: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/termlog.go","lineNumber":76,"sourceCode":"\t\ttlw.containers = make(map[int64]struct{})\n\t\tfor _, container := range containers {\n\t\t\ttlw.containers[container.ID] = struct{}{}\n\t\t}\n\t\tif _, ok := tlw.containers[containerID]; !ok {\n\t\t\treturn 0, fmt.Errorf(\"container not found\")\n\t\t}\n\t}\n\n\ttermLog, err := tlw.db.CreateTermLog(ctx, database.CreateTermLogParams{\n\t\tType:        msgType,\n\t\tText:        database.SanitizeUTF8(msg),\n\t\tContainerID: containerID,\n\t\tFlowID:      tlw.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 termlog: %w\", err)\n\t}\n\n\ttlw.pub.TerminalLogAdded(ctx, termLog)\n\n\treturn termLog.ID, nil\n}\n\nfunc (tlw *flowTermLogWorker) GetMsg(ctx context.Context, msgID int64) (database.Termlog, error) {\n\tmsg, err := tlw.db.GetTermLog(ctx, msgID)\n\tif err != nil {\n\t\treturn database.Termlog{}, fmt.Errorf(\"failed to get termlog: %w\", err)\n\t}\n\n\treturn msg, nil\n}\n\nfunc (tlw *flowTermLogWorker) GetContainers(ctx context.Context) ([]database.Container, error) {\n\tcontainers, err := tlw.db.GetFlowContainers(ctx, tlw.flowID)","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/termlog.go#L58-L94","documentation":"PutMsg inserts the terminal log row via db.CreateTermLog after the container check passes. This error wraps the underlying database failure: FK violation (e.g. task/subtask rows deleted concurrently), connection loss, context cancellation, or oversized/invalid text data (text is UTF-8-sanitized first, so encoding is usually not the cause).","triggerScenarios":"DB connection dropped during heavy terminal output; taskID/subtaskID pointing at rows deleted mid-execution; canceled context from a request timeout; violating a NOT NULL/length constraint on text.","commonSituations":"Long-running exec sessions surviving a Postgres restart; agents racing subtask completion/cleanup while still emitting output; very large paste/burst writes hitting timeouts.","solutions":["Inspect the wrapped cause to distinguish FK vs connection vs cancellation","Validate taskID/subtaskID still exist and belong to the flow before logging","Add bounded retry with backoff for transient driver errors on terminal writes","Keep ctx alive for log writes (background context) so request cancellation doesn't drop terminal history","Check DB logs for the failing constraint and fix the caller's ID lifecycle"],"exampleFix":"// before\nif _, err := termWorker.PutMsg(ctx, t, line, cid, &taskID, &subtaskID); err != nil { return err }\n// after\nif _, err := termWorker.PutMsg(context.WithoutCancel(ctx), t, line, cid, &taskID, &subtaskID); err != nil {\n    if isTransient(err) { return retryPutMsg(t, line, cid, taskID, subtaskID) }\n    return err\n}","handlingStrategy":"retry","validationCode":"func canWriteTermLog(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 err }\n    }\n    if subtaskID != nil {\n        if _, err := q.GetSubtask(ctx, *subtaskID); err != nil { return 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":"err := retry(3, backoff, func() error {\n    _, err := w.PutMsg(ctx, msgType, line, containerID, taskID, subtaskID)\n    if isForeignKeyErr(err) || errors.Is(err, context.Canceled) { return stop(err) }\n    return err // transient errors are retried\n})","preventionTips":["Use context.WithoutCancel for terminal history writes so request teardown doesn't drop logs","Retry only transient DB errors (driver.ErrBadConn, connection reset); never retry FK violations","Ensure subtask cleanup waits for pending terminal writes","Cap message size and flush in batches to avoid timeouts under output bursts","Monitor Postgres availability; alert on restarts during active flows"],"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"}