{"record":{"id":"dc0ca8edc194115c","repo":"gastownhall/beads","slug":"s-w-dc0ca8","errorCode":null,"errorMessage":"%s: %w","messagePattern":"%s: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/schema.go","lineNumber":820,"sourceCode":"\t}\n\treturn nil\n}\n\nfunc unstageIgnoredTables(ctx context.Context, db DBConn) error {\n\ttables, err := existingIgnoredTables(ctx, db)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn unstagePreExistingTables(ctx, db, tables)\n}\n\nfunc dirtyTableSignatures(ctx context.Context, db DBConn, tables map[string]dirtyTableState) (map[string]string, error) {\n\tsignatures := make(map[string]string, len(tables))\n\tnames := sortedDirtyTableNames(tables)\n\tfor _, table := range names {\n\t\tsignature, err := dirtyTableSignature(ctx, db, table)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%s: %w\", table, err)\n\t\t}\n\t\tsignatures[table] = signature\n\t}\n\treturn signatures, nil\n}\n\nfunc changedDirtyTableSignatures(ctx context.Context, db DBConn, before map[string]string) ([]string, error) {\n\tvar changed []string\n\tnames := sortedSignatureTableNames(before)\n\tfor _, table := range names {\n\t\tsignature, err := dirtyTableSignature(ctx, db, table)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%s: %w\", table, err)\n\t\t}\n\t\tif signature != before[table] {\n\t\t\tchanged = append(changed, table)\n\t\t}\n\t}","sourceCodeStart":802,"sourceCodeEnd":838,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/schema.go#L802-L838","documentation":"dirtyTableSignatures fingerprints each pre-existing dirty table by hashing the full dolt_diff('HEAD','WORKING') diff, so MigrateUp can later prove migrations did not touch user data. When computing a table's signature fails (dirtyTableSignature error — unsafe name, or dolt_diff query/scan failure), the error is wrapped with the table name: \"<table>: %w\" and MigrateUp aborts before running any migration.","triggerScenarios":"MigrateUp calls dirtyTableSignatures over committableDirtyTables output; a table's dolt_diff query errors (e.g. table dropped/renamed between status read and diff, query error from the Dolt engine, or row scan/type error), or dirtyTableSignature rejects the table name.","commonSituations":"Concurrent process mutates the schema (drops/renames tables) between dolt_status read and diff computation; a table in dolt_status with characters the dolt_diff call cannot handle; Dolt engine version returning an unexpected dolt_diff result shape; corrupted working set.","solutions":["Identify the table named in the message and the wrapped cause; run `dolt status` and `dolt diff <table>` manually in the .beads database directory to reproduce.","Stop concurrent writers (other `bd` instances, background sync) and retry — the table may have disappeared mid-scan.","Commit or clean the dirty table's changes yourself (dolt add/commit or checkout) so it no longer appears dirty, then rerun `bd`.","If dolt_diff itself errors, check the embedded Dolt version matches what beads expects and upgrade if mismatched.","Inspect DirtyTablesError guidance (#4566): pre-existing dirty tables are allowed but must remain unchanged; stabilizing the working set clears this path."],"exampleFix":"// before: MigrateUp fails while fingerprinting a dirty table\n_, err := bd.Open(dbPath)\n// after: settle the dirty table first via dolt, then open\n//   cd .beads && dolt add issues && dolt commit -m \"user data\"\n_, err = bd.Open(dbPath)","handlingStrategy":"validation","validationCode":"// Go: verify every dirty table name is fingerprintable before migrating\nrows, _ := db.QueryContext(ctx, \"SELECT table_name FROM dolt_status WHERE dirty\")\nfor rows.Next() {\n    var name string\n    _ = rows.Scan(&name)\n    if !isSafeTableName(name) {\n        return fmt.Errorf(\"rename table %q before migrating\", name)\n    }\n}","typeGuard":"// Go: narrow per-table signature failures (format: \"<table>: <cause>\")\nfunc isDirtyTableSignatureError(err error) (table string, ok bool) {\n    if err == nil { return \"\", false }\n    msg := err.Error()\n    // heuristic: this family surfaces as \"committing migrations\" wrapper's cause\n    if i := strings.Index(msg, \": \"); i > 0 {\n        return msg[:i], true\n    }\n    return \"\", false\n}","tryCatchPattern":"err := schema.MigrateUp(ctx, db)\nif err != nil {\n    var dirtyErr *schema.DirtyTablesError\n    if errors.As(err, &dirtyErr) {\n        // pre-existing dirty tables: commit/reset them, then retry\n    } else {\n        // signature/diff failure: stop concurrent writers and retry\n    }\n}","preventionTips":["Avoid dropping/renaming tables in the beads DB while bd is starting","Commit or clean dirty tables before running bd so the fingerprint pass is a no-op","Run one bd process at a time against a given database","Keep the embedded Dolt engine at the version beads expects"],"tags":["database","dolt","migration","dirty-working-set"],"backgroundTag":"dolt-diff-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}