{"record":{"id":"23d6eacbcc455478","repo":"gastownhall/beads","slug":"issue-s-has-invalid-s-boolean","errorCode":null,"errorMessage":"issue %s has invalid %s boolean","messagePattern":"issue (.+?) has invalid (.+?) boolean","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":575,"sourceCode":"\t}\n\tif issue.CreatedAt.IsZero() || issue.UpdatedAt.IsZero() {\n\t\treturn fmt.Errorf(\"legacy SQLite issue %s has invalid created_at or updated_at\", issue.ID)\n\t}\n\treturn nil\n}\n\n// checkRemovedFields rejects legacy rows that populate columns the current\n// schema no longer supports, then validates the tri-state boolean columns.\nfunc (x legacyExtras) checkRemovedFields(issue *types.Issue) error {\n\tif nonempty(x.closedBy, x.deletedBy, x.deleteReason, x.originalType, x.hookBead, x.roleBead, x.agentState, x.lastActivity, x.roleType, x.rig) || x.deletedAt.Valid || x.crystallizes.Int64 != 0 || x.quality.Valid || (issue.SourceRepo != \"\" && issue.SourceRepo != \".\") {\n\t\treturn fmt.Errorf(\"legacy SQLite issue %s uses unsupported removed fields\", issue.ID)\n\t}\n\tfor _, b := range []struct {\n\t\tname string\n\t\tv    sql.NullInt64\n\t}{{\"ephemeral\", x.ephemeral}, {\"pinned\", x.pinned}, {\"is_template\", x.template}} {\n\t\tif b.v.Valid && b.v.Int64 != 0 && b.v.Int64 != 1 {\n\t\t\treturn fmt.Errorf(\"issue %s has invalid %s boolean\", issue.ID, b.name)\n\t\t}\n\t}\n\treturn nil\n}\n\ntype currentVarchar struct {\n\tname, value string\n\tmaxRunes    int\n}\n\ntype currentString struct {\n\tname, value string\n}\n\nfunc validateIssueStrings(issue *types.Issue) error {\n\tfields := []currentString{\n\t\t{\"issue id\", issue.ID},\n\t\t{\"issue title\", issue.Title},","sourceCodeStart":557,"sourceCodeEnd":593,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L557-L593","documentation":"checkRemovedFields validates the tri-state boolean columns ephemeral, pinned, and is_template. Each may be NULL (unset), 0, or 1; any other non-zero integer (e.g. 2, -1) is invalid. This error names the offending issue and the specific boolean column with an out-of-range value.","triggerScenarios":"A legacy row stores a value other than 0, 1, or NULL in ephemeral, pinned, or is_template — commonly -1 used as a 'false' sentinel, or 2 from a bitfield-style writer.","commonSituations":"External scripts treating the columns as general integers; data imported from another tracker mapping booleans to non-0/1 integers; corruption.","solutions":["Inspect the value: sqlite3 legacy.db \"SELECT id, ephemeral, pinned, is_template FROM issues WHERE id='<id>'\"","Normalize the offending column to 0 or 1 (map any non-zero truthy to 1, otherwise 0), e.g. UPDATE issues SET ephemeral = CASE WHEN ephemeral = 0 THEN 0 ELSE 1 END WHERE id='<id>'","Or set the column to NULL to leave the tri-state unset","Re-run the migration"],"exampleFix":"// before\nephemeral = -1\n// after\nUPDATE issues SET ephemeral = 0 WHERE id = '<id>';","handlingStrategy":"validation","validationCode":"-- run before migration\nSELECT id FROM issues\nWHERE (ephemeral IS NOT NULL AND ephemeral NOT IN (0,1))\n   OR (pinned   IS NOT NULL AND pinned   NOT IN (0,1))\n   OR (is_template IS NOT NULL AND is_template NOT IN (0,1));","typeGuard":"def tri_state_ok(v) -> bool:\n    return v is None or v in (0, 1)","tryCatchPattern":null,"preventionTips":["Treat tri-state booleans strictly as NULL/0/1 in all writers","Normalize -1/other sentinels during any pre-migration ETL","Add CHECK constraints to the legacy DB: CHECK (col IN (0,1)) or NULL"],"tags":["migration","sqlite","validation","booleans"],"backgroundTag":"invalid-boolean-value","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}