{"record":{"id":"2caa0e962a344e59","repo":"gastownhall/beads","slug":"reading-aux-rekey-sentinel-w","errorCode":null,"errorMessage":"reading aux rekey sentinel: %w","messagePattern":"reading aux rekey sentinel: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/aux_row_id_backfill.go","lineNumber":233,"sourceCode":"\t\t}\n\t}\n\treturn wrote, nil\n}\n\nfunc rekeyAuxRowIDsPending(ctx context.Context, db DBConn, mainVersionBefore int, pass auxRekeyPass, pending []int) (bool, error) {\n\tmarkerPending := false\n\tfor _, v := range pending {\n\t\tif v == pass.markerVersion {\n\t\t\tmarkerPending = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !markerPending {\n\t\treturn false, nil\n\t}\n\tresume, err := auxRekeyResumePending(ctx, db, pass.sentinelKey)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"reading aux rekey sentinel: %w\", err)\n\t}\n\t// The fresh-clone skip must not fire on a lineage whose previous pass\n\t// crashed mid-rekey: that pass already advanced the main cursor past the\n\t// shipped version, but its sentinel proves the rewrite never finished\n\t// (bd-578h9.16).\n\tif mainVersionBefore >= pass.shippedMainVersion && !resume {\n\t\treturn false, nil\n\t}\n\n\t// Sentinel before the first UPDATE: a crash anywhere in the rewrite\n\t// leaves it set, so the next pass resumes (the rewrite is idempotent)\n\t// instead of recording the marker over partially re-keyed rows.\n\tif err := setAuxRekeyInProgress(ctx, db, pass.sentinelKey); err != nil {\n\t\treturn false, fmt.Errorf(\"recording aux rekey sentinel: %w\", err)\n\t}\n\n\twrote := false\n\tfor _, t := range auxRekeyTables {","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/aux_row_id_backfill.go#L215-L251","documentation":"Before running a rekey pass, rekeyAuxRowIDsPending probes the clone-local local_metadata table (via auxRekeyResumePending) to detect a crashed previous run's in-progress sentinel — a set sentinel forces resume even when the main cursor is past the shipped version. This error wraps failure of that probe (either the INFORMATION_SCHEMA table probe or the sentinel COUNT query), so the pass refuses to decide between skip and resume rather than guessing and risking a marker recorded over partially re-keyed rows.","triggerScenarios":"A pass whose marker migration is pending, while the sentinel probe queries fail: INFORMATION_SCHEMA.TABLES or local_metadata SELECT errors due to connection loss, privileges, or corruption.","commonSituations":"A clone resuming after a crash (sentinel path exercised) hitting a transient DB failure; permission-restricted migration user without SELECT on local_metadata; INFORMATION_SCHEMA access restricted by hosting policy.","solutions":["Unwrap the driver error and re-run MigrateUp — the probe is read-only and safe to repeat.","Grant SELECT on local_metadata and INFORMATION_SCHEMA access to the migration user.","If local_metadata exists but is corrupt, drop it (it is clone-local and recreated by setAuxRekeyInProgress/EnsureIgnoredTables) and re-run.","Check for concurrent processes holding table locks during the probe."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"var t int\nerr := db.QueryRow(`SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES\n WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'local_metadata'`).Scan(&t)\nif err != nil { return fmt.Errorf(\"cannot probe schema before migration: %w\", err) }","typeGuard":null,"tryCatchPattern":"if err := migrateUp(ctx, db); err != nil {\n    if strings.Contains(err.Error(), \"reading aux rekey sentinel\") {\n        // probe failed; do NOT manually clear the sentinel — retry after fixing the DB error\n        return fmt.Errorf(\"sentinel probe failed; fix storage and re-run to resume safely: %w\", err)\n    }\n    return err\n}","preventionTips":["Grant SELECT on local_metadata and full INFORMATION_SCHEMA visibility to the migration user.","After a crash, re-run MigrateUp promptly so the sentinel-resume path completes.","Never DELETE the sentinel row by hand to 'unstick' a migration — the rewrite is idempotent and resume is the safe path.","Avoid restricting INFORMATION_SCHEMA access in hardened hosting setups."],"tags":["database","migration","dolt","crash-recovery"],"backgroundTag":"migration-sentinel-probe-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}