{"record":{"id":"9a117ee8ef6cd179","repo":"vxcontrol/pentagi","slug":"failed-to-get-user-d-w","errorCode":null,"errorMessage":"failed to get user %d: %w","messagePattern":"failed to get user (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/flow.go","lineNumber":178,"sourceCode":"\t// DeleteFlow is a soft delete and the listings filter on deleted_at, so this is what keeps a flow\n\t// the caller was told it never got out of the UI. Disarmed once the worker goroutine owns the flow —\n\t// from there a failure is the worker's to unwind, not ours.\n\tcleanupFlow := true\n\tdefer func() {\n\t\tif err == nil || !cleanupFlow {\n\t\t\treturn\n\t\t}\n\n\t\t// The caller's context is usually already cancelled by whatever failed.\n\t\tif _, cerr := fwc.db.DeleteFlow(context.WithoutCancel(ctx), flowID); cerr != nil {\n\t\t\tlogger.WithError(cerr).Error(\"failed to drop the flow left behind by a failed start\")\n\t\t}\n\t}()\n\n\tuser, err := fwc.db.GetUser(ctx, fwc.userID)\n\tif err != nil {\n\t\tlogger.WithError(err).Error(\"failed to get user\")\n\t\treturn nil, fmt.Errorf(\"failed to get user %d: %w\", fwc.userID, err)\n\t}\n\n\tctx, observation := obs.Observer.NewObservation(ctx,\n\t\tlangfuse.WithObservationTraceContext(\n\t\t\tlangfuse.WithTraceName(fmt.Sprintf(\"%s%d flow worker\", fwc.cfg.TenantLabel(), flow.ID)),\n\t\t\tlangfuse.WithTraceUserID(tenantUserID(fwc.cfg, user.Mail)),\n\t\t\tlangfuse.WithTraceTags(tenantTags(fwc.cfg, \"controller\", \"flow\")),\n\t\t\tlangfuse.WithTraceInput(fwc.input),\n\t\t\tlangfuse.WithTraceSessionID(fwc.cfg.ScopedName(fmt.Sprintf(\"flow-%d\", flow.ID))),\n\t\t\tlangfuse.WithTraceMetadata(tenantMeta(fwc.cfg, langfuse.Metadata{\n\t\t\t\t\"flow_id\":       flow.ID,\n\t\t\t\t\"user_id\":       fwc.userID,\n\t\t\t\t\"user_email\":    user.Mail,\n\t\t\t\t\"user_name\":     user.Name,\n\t\t\t\t\"user_hash\":     user.Hash,\n\t\t\t\t\"user_role\":     user.RoleName,\n\t\t\t\t\"provider_name\": fwc.prvname.String(),\n\t\t\t\t\"provider_type\": fwc.prvtype.String(),","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/flow.go#L160-L196","documentation":"Right after creating the flow row, NewFlowWorker fetches the owning user with db.GetUser(ctx, fwc.userID) to build tenant/langfuse metadata. If the user row cannot be read, it wraps the error as 'failed to get user %d'. The flow row was already created but the worker start aborts (and the deferred cleanup soft-deletes the flow).","triggerScenarios":"db.GetUser fails for fwc.userID: user hard-deleted between auth and flow creation, DB connection error, context cancelled, or callers passing a userID that does not exist (0 or stale).","commonSituations":"Multi-instance setups where a user was deleted on another node; API token auth with a userID referencing a purged user; transient Postgres connection drop; passing userID=0 from an uninitialized auth context.","solutions":["Confirm the user with that ID exists (SELECT * FROM users WHERE id = ?) before creating flows.","Check the wrapped cause in logs to distinguish not-found (sql.ErrNoRows) from connection errors.","Verify the auth middleware always populates a valid userID in the request context.","If users are soft-deleted elsewhere, purge their API tokens/sessions too so stale IDs aren't used.","Retry transient DB errors; check Postgres health if all requests fail."],"exampleFix":"// before\nuserID := int64(0) // unauthenticated/uninitialized context\nworker, err := controller.NewFlowWorker(ctx, fwcWithUser(userID))\n// after\nif userID <= 0 { return http.Error(w, \"unauthorized\", 401) }\nif _, err := db.GetUser(ctx, userID); err != nil {\n    return http.Error(w, \"unknown user\", 404)\n}\nworker, err := controller.NewFlowWorker(ctx, fwcWithUser(userID))","handlingStrategy":"validation","validationCode":"user, err := db.GetUser(ctx, userID)\nif err != nil {\n    return fmt.Errorf(\"cannot create flow for user %d: %w\", userID, err)\n}","typeGuard":"func userExists(ctx context.Context, db DB, id int64) bool {\n    _, err := db.GetUser(ctx, id)\n    return err == nil\n}","tryCatchPattern":"fw, err := controller.NewFlowWorker(ctx, fwc)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to get user\") {\n        return http.StatusNotFound // bad/stale userID\n    }\n    return http.StatusInternalServerError\n}","preventionTips":["Validate userID from auth middleware before creating flows","Soft-delete users and invalidate their tokens/sessions in the same transaction","Avoid hard-deleting users with active flows","Distinguish sql.ErrNoRows from connection errors when handling"],"tags":["go","database","user-lookup","flow-worker"],"backgroundTag":"record-not-found","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}