{"record":{"id":"fa3c7d0d7d601a32","repo":"iflytek/astron-agent","slug":"lock-tenant-bootstrap-app-failed-w","errorCode":null,"errorMessage":"lock tenant bootstrap app failed: %w","messagePattern":"lock tenant bootstrap app failed: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/tenant/tools/database/bootstrap_credentials.go","lineNumber":147,"sourceCode":"\t\tfalse,\n\t\t\"星辰租户\",\n\t\tfalse,\n\t\t\"\",\n\t); err != nil {\n\t\treturn fmt.Errorf(\"ensure tenant bootstrap app failed: %w\", err)\n\t}\n\n\t// Serialize reconciliation across replicas on the reserved app row before\n\t// taking any auth-index gap locks or rotating managed credentials.\n\tvar lockedAppID string\n\tvar lockedAppDisabled sql.NullBool\n\tvar lockedAppDeleted sql.NullBool\n\tif err := transaction.QueryRowContext(\n\t\tctx,\n\t\t`SELECT app_id, is_disable, is_delete FROM tb_app WHERE app_id = ? FOR UPDATE`,\n\t\tcredentials.TenantID,\n\t).Scan(&lockedAppID, &lockedAppDisabled, &lockedAppDeleted); err != nil {\n\t\treturn fmt.Errorf(\"lock tenant bootstrap app failed: %w\", err)\n\t}\n\tif lockedAppID != credentials.TenantID {\n\t\treturn errors.New(\"locked tenant bootstrap app does not match the reserved tenant ID\")\n\t}\n\tif !lockedAppDisabled.Valid || lockedAppDisabled.Bool ||\n\t\t!lockedAppDeleted.Valid || lockedAppDeleted.Bool {\n\t\treturn errors.New(\"reserved tenant bootstrap app is disabled or deleted\")\n\t}\n\treturn nil\n}\n\nfunc findTenantBootstrapCredential(\n\tctx context.Context,\n\ttransaction bootstrapTransaction,\n\tcredentials config.TenantBootstrapCredentials,\n) (bool, error) {\n\tvar collisionOwner string\n\terr := transaction.QueryRowContext(","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/tenant/tools/database/bootstrap_credentials.go#L129-L165","documentation":"After ensuring the bootstrap app row, the code takes a row lock with SELECT ... FOR UPDATE on tb_app for the reserved tenant ID and scans it back; any query/scan failure is wrapped as 'lock tenant bootstrap app failed'. The lock serializes concurrent replicas before credential rotation.","triggerScenarios":"QueryRowContext(...).Scan fails because the row vanished between insert and lock (concurrent delete), a connection error occurred, the MySQL user lacks SELECT/LOCK privilege, or Scan types mismatch the columns.","commonSituations":"Another process deleted the reserved row mid-transaction; connection dropped during long lock waits (innodb_lock_wait_timeout exceeded surfaces as a query error); replicas racing at startup contend on the same row and hit lock timeouts.","solutions":["Inspect the wrapped cause: on lock-wait timeout, check innodb_lock_wait_timeout and which transaction holds the row (SHOW ENGINE INNODB STATUS).","Ensure no other job deletes or modifies the reserved tenant row concurrently; protect it from cleanup scripts.","Verify the bootstrap user has SELECT and locking capability on tb_app.","Retry bootstrap after transient contention; the transactional design makes re-runs safe."],"exampleFix":"// before: row deleted concurrently, Scan returns sql.ErrNoRows\nerr := transaction.QueryRowContext(ctx, `SELECT app_id, is_disable, is_delete FROM tb_app WHERE app_id = ? FOR UPDATE`, id).Scan(...)\n// after: distinguish missing row from real failures\nrow := transaction.QueryRowContext(ctx, lockSQL, id)\nif err := row.Scan(&lockedAppID, &lockedAppDisabled, &lockedAppDeleted); err != nil {\n    if errors.Is(err, sql.ErrNoRows) {\n        return fmt.Errorf(\"reserved tenant app %s disappeared during bootstrap\", id)\n    }\n    return fmt.Errorf(\"lock tenant bootstrap app failed: %w\", err)\n}","handlingStrategy":"retry","validationCode":"var one int\nif err := db.QueryRow(`SELECT 1 FROM tb_app WHERE app_id = ?`, reservedID).Scan(&one); err != nil {\n    return fmt.Errorf(\"reserved app missing before bootstrap: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"row := tx.QueryRowContext(ctx, lockSQL, id)\nerr := row.Scan(&a, &b, &c)\nswitch {\ncase errors.Is(err, sql.ErrNoRows):\n    return fmt.Errorf(\"reserved app vanished; rerun bootstrap\")\ncase isLockWaitTimeout(err):\n    return retryErr // backoff and retry the whole transaction\ncase err != nil:\n    return fmt.Errorf(\"lock tenant bootstrap app failed: %w\", err)\n}","preventionTips":["Prevent concurrent replicas from deleting the reserved tenant row.","Tune innodb_lock_wait_timeout for bootstrap workloads.","Keep transactions brief and serialize bootstrap via an external lease if many replicas start at once."],"tags":["go","mysql","locking","transaction"],"backgroundTag":"database-query-failed","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}