{"record":{"id":"e5188ace8b0f0c2c","repo":"bytebase/bytebase","slug":"failed-to-update-active-vcs-provider-user","errorCode":null,"errorMessage":"failed to update active VCS provider user","messagePattern":"failed to update active VCS provider user","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/store/vcs_provider_user.go","lineNumber":69,"sourceCode":"\tif payload == nil {\n\t\tpayload = &storepb.VCSProviderUserPayload{}\n\t}\n\tpayloadBytes, err := protojson.Marshal(payload)\n\tif err != nil {\n\t\treturn false, errors.Wrapf(err, \"failed to marshal VCS provider user payload\")\n\t}\n\n\tvcsType := user.VCSType.String()\n\tvar refreshed bool\n\tif err := s.GetDB().QueryRowContext(ctx, `\n\t\tUPDATE vcs_provider_user\n\t\tSET last_seen_at = now(), payload = $4\n\t\tWHERE workspace = $1 AND vcs_type = $2 AND user_id = $3\n\t\t\tAND last_seen_at >= now() - make_interval(secs => $5)\n\t\tRETURNING true\n\t`, workspace, vcsType, user.UserID, payloadBytes, activeWindow.Seconds()).Scan(&refreshed); err != nil {\n\t\tif err != sql.ErrNoRows {\n\t\t\treturn false, errors.Wrapf(err, \"failed to update active VCS provider user\")\n\t\t}\n\t} else {\n\t\treturn true, nil\n\t}\n\n\ttx, err := s.GetDB().BeginTx(ctx, nil)\n\tif err != nil {\n\t\treturn false, errors.Wrapf(err, \"failed to begin transaction\")\n\t}\n\tdefer tx.Rollback()\n\n\tif err := AcquireAdvisoryXactLockWithStringKey(ctx, tx, AdvisoryLockKeyVCSProviderUser, workspace); err != nil {\n\t\treturn false, errors.Wrapf(err, \"failed to acquire VCS provider user lock\")\n\t}\n\n\tvar active bool\n\tif err := tx.QueryRowContext(ctx, `\n\t\tSELECT last_seen_at >= now() - make_interval(secs => $4)","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/store/vcs_provider_user.go#L51-L87","documentation":"TouchVCSProviderUser first attempts a fast-path UPDATE ... RETURNING true to refresh an already-active VCS provider user. If Scan returns an error other than sql.ErrNoRows — the statement itself failed rather than simply matching no row — the store wraps it as \"failed to update active VCS provider user\". sql.ErrNoRows is deliberately treated as \"user not currently active\" and falls through to the transactional insert/reactivate path.","triggerScenarios":"Calling TouchVCSProviderUser during a metadata DB outage or connection-pool exhaustion; the request context is cancelled while the UPDATE executes; the vcs_provider_user table is missing (unmigrated schema); parameter type/constraint errors.","commonSituations":"License-limit check during a Postgres failover; timeouts under load when many VCS users hit the GitOps endpoint concurrently; app running against a schema missing vcs_provider_user; transient context deadline cancellation.","solutions":["Check metadata DB health and retry; transient connection/context errors usually resolve on retry.","Verify vcs_provider_user exists and matches LATEST.sql; run migrations if the table is missing.","Increase context timeouts for the VCS touch path if deadline cancellations occur under load.","Inspect the wrapped driver error (pg error code) to pinpoint server-side causes like lock contention or statement timeouts."],"exampleFix":"// before\nrefreshed, err := store.TouchVCSProviderUser(ctx, ws, user, window, limit)\nif err != nil {\n\treturn fmt.Errorf(\"touch failed: %w\", err)\n}\n// after (retry transient failures before giving up)\nvar refreshed bool\nbackoff := 100 * time.Millisecond\nfor i := 0; i < 3; i++ {\n\tr, err := store.TouchVCSProviderUser(ctx, ws, user, window, limit)\n\tif err == nil {\n\t\trefreshed = r\n\t\tbreak\n\t}\n\tif !isTransientNetErr(err) || i == 2 {\n\t\treturn fmt.Errorf(\"touch failed: %w\", err)\n\t}\n\ttime.Sleep(backoff)\n\tbackoff *= 2\n}","handlingStrategy":"retry","validationCode":"// preflight: ensure the table exists and inputs are non-empty before touching\nvar exists bool\n_ = db.QueryRowContext(ctx, \"SELECT to_regclass('vcs_provider_user') IS NOT NULL\").Scan(&exists)\nif !exists || workspace == \"\" || user.UserID == \"\" || user.VCSType == v1pb.VCSType_VCS_TYPE_UNSPECIFIED {\n\treturn errors.New(\"invalid touch inputs or missing table\")\n}","typeGuard":null,"tryCatchPattern":"refreshed, err := store.TouchVCSProviderUser(ctx, ws, user, window, limit)\nif err != nil {\n\tif isTransientNetErr(err) {\n\t\trefreshed, err = store.TouchVCSProviderUser(retryCtx, ws, user, window, limit)\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"touch vcs provider user: %w\", err)\n\t}\n}","preventionTips":["Pass a context with an adequate deadline; cancellation during the fast-path UPDATE surfaces through this error.","Ensure migrations run before any GitOps/VCS traffic reaches the instance.","Distinguish sql.ErrNoRows (expected, handled internally) from real failures — only retry the latter.","Monitor pool saturation; this UPDATE runs on every VCS-authenticated request, so contention under load is a common trigger."],"tags":["database","postgres","update","vcs"],"backgroundTag":"database-write-failed","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}