{"record":{"id":"cad9fda9fc2dc43f","repo":"gastownhall/beads","slug":"invalid-ref-w","errorCode":null,"errorMessage":"invalid ref: %w","messagePattern":"invalid ref: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/migration_content_hashes.go","lineNumber":50,"sourceCode":"// ReadMigrationContentHashes reads version -> content_hash from schema_migrations,\n// either at HEAD (ref == \"\") or AS OF ref (e.g. \"remotes/origin/main\"). NULL/empty\n// hashes are dropped. It returns an error when the table, column, or ref is\n// unavailable; the caller classifies it with RemoteRefUnavailableErr /\n// MissingMigrationObjectErr.\n//\n// Dolt requires a literal ref in AS OF: bind parameters (including inside CONCAT)\n// fail server-side with `unbound variable \"v1\" in query`, so the validated ref is\n// interpolated into the SQL text (bd-6dnrw.27).\nfunc ReadMigrationContentHashes(ctx context.Context, db DBConn, ref string) (map[int]string, error) {\n\tvar (\n\t\trows *sql.Rows\n\t\terr  error\n\t)\n\tif ref == \"\" {\n\t\trows, err = db.QueryContext(ctx, \"SELECT version, content_hash FROM schema_migrations\")\n\t} else {\n\t\tif verr := validateMigrationRef(ref); verr != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid ref: %w\", verr)\n\t\t}\n\t\t// A cached ref may legitimately predate schema_migrations or its\n\t\t// content_hash column. Probe the historical shape before selecting from\n\t\t// it: letting the SELECT fail emits a Dolt server warning for every\n\t\t// read-only doctor run.\n\t\t//nolint:gosec // G201: ref is validated above — AS OF requires a literal, not a bind param\n\t\thasTable, queryErr := queryHasRows(ctx, db,\n\t\t\tfmt.Sprintf(\"SHOW TABLES AS OF '%s' LIKE 'schema_migrations'\", ref))\n\t\tif queryErr != nil {\n\t\t\treturn nil, queryErr\n\t\t}\n\t\tif !hasTable {\n\t\t\treturn nil, fmt.Errorf(\"table not found: schema_migrations at %q\", ref)\n\t\t}\n\t\t//nolint:gosec // G201: ref is validated above — AS OF requires a literal, not a bind param\n\t\thasContentHash, queryErr := queryHasRows(ctx, db,\n\t\t\tfmt.Sprintf(\"SHOW COLUMNS FROM schema_migrations AS OF '%s' LIKE 'content_hash'\", ref))\n\t\tif queryErr != nil {","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/migration_content_hashes.go#L32-L68","documentation":"ReadMigrationContentHashes wraps any validation failure from validateMigrationRef in \"invalid ref: %w\". The underlying cause (empty, too long, or bad characters) is always wrapped, so use errors.Is/Unwrap to see the specific reason. It is the entry guard before interpolating the ref into AS OF queries.","triggerScenarios":"Calling ReadMigrationContentHashes (directly or via remoteMaxAtRef / routeSmartGate) with a ref that fails validation: empty, >128 chars, or containing characters outside [a-zA-Z0-9_./-].","commonSituations":"Same as the underlying validators: blank config values, pasted URLs, user-supplied refs with special characters; surfaced through doctor's read-only comparison of remote state.","solutions":["Fix the ref to be a non-empty, <=128-char, allowlist-conforming string like \"remotes/origin/main\".","Check the wrapped cause with errors.Is to know which rule failed.","Pass \"\" only when HEAD is intended via the ref==\"\" code path.","Sanitize ref inputs at the boundary (CLI/config) before calling."],"exampleFix":"// before\nhashes, err := schema.ReadMigrationContentHashes(ctx, db, raw)\n// after\nref := strings.TrimSpace(raw)\nif ref != \"\" && (len(ref) > 128 || !refRe.MatchString(ref)) {\n    return fmt.Errorf(\"bad ref %q\", ref)\n}\nhashes, err := schema.ReadMigrationContentHashes(ctx, db, ref)","handlingStrategy":"validation","validationCode":"func migrationRefOK(ref string) bool {\n    return ref == \"\" || (len(ref) <= 128 &&\n        regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`).MatchString(ref))\n}","typeGuard":"func validRef(ref string) bool {\n    return ref == \"\" || (len(ref) <= 128 &&\n        regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`).MatchString(ref))\n}","tryCatchPattern":"if err != nil {\n    var unwrapped = errors.Unwrap(err)\n    // inspect wrapped cause: empty / too long / invalid format\n}","preventionTips":["Validate refs once at the CLI/config boundary.","Unwrap the error to identify which rule failed before retrying.","Use \"\" only intentionally (HEAD path)."],"tags":["validation","migration","git-ref"],"backgroundTag":"invalid-git-ref","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}