{"record":{"id":"3592271085b73ddf","repo":"gastownhall/beads","slug":"checking-s-content-hash-w","errorCode":null,"errorMessage":"checking %s.content_hash: %w","messagePattern":"checking (.+?)\\.content_hash: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/schema.go","lineNumber":1104,"sourceCode":"\n// hasContentHashColumn reports whether the cursor table already carries the\n// content_hash column. A not-yet-created table simply reports false.\n//\n// It probes a single table with SHOW COLUMNS rather than INFORMATION_SCHEMA.COLUMNS,\n// whose predicate Dolt does not push down. The LIKE narrows the result set, but\n// we still compare the Field name exactly because '_' is a LIKE single-character\n// wildcard.\nfunc (m migrationSource) hasContentHashColumn(ctx context.Context, db DBConn) (bool, error) {\n\t//nolint:gosec // G201: m.cursorTable is a hardcoded constant; the LIKE literal is fixed.\n\trows, err := db.QueryContext(ctx, \"SHOW COLUMNS FROM \"+m.cursorTable+\" LIKE 'content_hash'\")\n\tif err != nil {\n\t\t// SHOW COLUMNS errors on a missing table; the old INFORMATION_SCHEMA\n\t\t// probe returned count 0 instead. Preserve that: an absent cursor table\n\t\t// has no content_hash column.\n\t\tif dberrors.IsTableNotExist(err) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, fmt.Errorf(\"checking %s.content_hash: %w\", m.cursorTable, err)\n\t}\n\tdefer func() { _ = rows.Close() }()\n\n\tcols, err := rows.Columns()\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"checking %s.content_hash: %w\", m.cursorTable, err)\n\t}\n\t// SHOW COLUMNS returns Field, Type, Null, Key, Default, Extra (and possibly\n\t// more on some servers); scan every column into RawBytes and read the first\n\t// (\"Field\"), which is the column name.\n\tcells := make([]sql.RawBytes, len(cols))\n\tdest := make([]any, len(cols))\n\tfor i := range cells {\n\t\tdest[i] = &cells[i]\n\t}\n\tfor rows.Next() {\n\t\tif err := rows.Scan(dest...); err != nil {\n\t\t\treturn false, fmt.Errorf(\"checking %s.content_hash: %w\", m.cursorTable, err)","sourceCodeStart":1086,"sourceCodeEnd":1122,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/schema.go#L1086-L1122","documentation":"hasContentHashColumn probes whether the migration cursor table has a content_hash column via `SHOW COLUMNS ... LIKE 'content_hash'`. When that SHOW COLUMNS query itself fails with an error that is NOT a table-does-not-exist error (table-not-exist is deliberately treated as 'false, nil'), the failure is wrapped as `checking <cursor>.content_hash: <cause>`.","triggerScenarios":"The `SHOW COLUMNS FROM <cursorTable> LIKE 'content_hash'` query fails for reasons other than a missing table: connection loss, permission denial, a corrupted catalog, or the session being poisoned after a prior failed statement on a pooled Dolt connection.","commonSituations":"Database user lacks privileges to run SHOW on the schema; transient network drop during bootstrap; Dolt server restarted mid-operation; the query runs against a backend where SHOW COLUMNS syntax/behavior differs.","solutions":["Check the wrapped cause for connection/permission errors and fix connectivity or GRANTs (the DB user needs SHOW/SELECT on the schema).","If the cause indicates a missing table unexpectedly, verify the cursor table name and that bootstrapSQL ran (CREATE TABLE IF NOT EXISTS).","Retry the migration on a fresh connection — pooled Dolt sessions can hold stale catalog snapshots after a failed statement.","Confirm you are on a supported Dolt server version; SHOW COLUMNS behavior differences can cause parse errors."],"exampleFix":"// before\nrows, err := db.QueryContext(ctx, \"SHOW COLUMNS FROM \"+m.cursorTable+\" LIKE 'content_hash'\")\n// after\nif err := m.ensureBootstrapTable(ctx, db); err != nil { // CREATE TABLE IF NOT EXISTS first\n    return false, err\n}\nrows, err := db.QueryContext(ctx, \"SHOW COLUMNS FROM \"+m.cursorTable+\" LIKE 'content_hash'\")","handlingStrategy":"try-catch","validationCode":"// verify access before bootstrap\nrows, err := db.QueryContext(ctx, \"SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 1\")\nif err != nil {\n    return fmt.Errorf(\"cannot read schema metadata: %w\", err)\n}\nrows.Close()","typeGuard":"func isTableNotExist(err error) bool { return dberrors.IsTableNotExist(err) }\n// treat isTableNotExist(err)==true as benign (column absent), everything else needs handling","tryCatchPattern":"_, err := MigrateUp(ctx, db)\nif err != nil && strings.Contains(err.Error(), \".content_hash\") {\n    if !dberrors.IsTableNotExist(errors.Unwrap(err)) {\n        log.Printf(\"content_hash probe failed (conn/perm issue): %v — retrying on fresh connection\", err)\n        _, err = MigrateUp(ctx, freshConnDB())\n    }\n}","preventionTips":["Ensure the app DB user has SHOW/SELECT privileges on the schema.","Use fresh connections for migration bootstrap; avoid sessions that previously executed failing statements on Dolt.","Run bootstrapSQL (CREATE TABLE IF NOT EXISTS) before probing cursor columns.","Match driver and Dolt server versions to supported combinations."],"tags":["database","dolt","migration","schema-probe"],"backgroundTag":"cursor-column-probe-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}