{"record":{"id":"f90c94eec46fdf1a","repo":"gastownhall/beads","slug":"legacy-sqlite-issue-s-is-a-tombstone","errorCode":null,"errorMessage":"legacy SQLite issue %s is a tombstone","messagePattern":"legacy SQLite issue (.+?) is a tombstone","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":556,"sourceCode":"func (x legacyExtras) applyCanonicalTimestamps(issue *types.Issue) error {\n\tvar err error\n\tif issue.CreatedAt, err = canonicalCurrentDatetime(issue.CreatedAt); err != nil {\n\t\treturn fmt.Errorf(\"legacy SQLite issue %s created_at: %w\", issue.ID, err)\n\t}\n\tif issue.UpdatedAt, err = canonicalCurrentDatetime(issue.UpdatedAt); err != nil {\n\t\treturn fmt.Errorf(\"legacy SQLite issue %s updated_at: %w\", issue.ID, err)\n\t}\n\treturn nil\n}\n\n// checkRequiredScalars enforces the non-empty ID, non-tombstone status, and\n// present created_at/updated_at invariants every legacy issue must satisfy.\nfunc checkRequiredScalars(issue *types.Issue) error {\n\tif issue.ID == \"\" {\n\t\treturn fmt.Errorf(\"legacy SQLite issue has empty ID\")\n\t}\n\tif issue.Status == \"tombstone\" {\n\t\treturn fmt.Errorf(\"legacy SQLite issue %s is a tombstone\", issue.ID)\n\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 {","sourceCodeStart":538,"sourceCodeEnd":574,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L538-L574","documentation":"checkRequiredScalars rejects tombstone rows: the legacy DB contains an issue whose status is 'tombstone'. Tombstones are deletion markers from the legacy sync model and are intentionally not migrated into the current schema; this row is skipped-by-error during validation.","triggerScenarios":"Migration reads a legacy issue row with status exactly 'tombstone' — i.e. an issue that was deleted in the legacy system but whose marker row remains in the issues table.","commonSituations":"Legacy databases that used tombstone rows for sync/replication deletion propagation; this is normal data in old multi-replica setups, not corruption.","solutions":["This is expected behavior — the tombstone should not be migrated; verify the corresponding entity was intentionally deleted","If the tombstone is stale and the live row was restored elsewhere, delete the tombstone row: sqlite3 legacy.db \"DELETE FROM issues WHERE id='<id>' AND status='tombstone'\"","If deletion was unintended, recreate the issue content before migrating","Re-run the migration"],"exampleFix":"// before\nstatus = 'tombstone' (row blocks migration validation)\n// after\nDELETE FROM issues WHERE id='<id>' AND status='tombstone';","handlingStrategy":"validation","validationCode":"-- identify tombstones before migrating\nSELECT id FROM issues WHERE status = 'tombstone';","typeGuard":"def is_migratable(row) -> bool:\n    return row.get('status') != 'tombstone'","tryCatchPattern":null,"preventionTips":["Review tombstone rows and confirm intended deletions before migrating","Purge resolved tombstones from the legacy DB as routine hygiene","Understand the legacy sync model so tombstones are not mistaken for data loss"],"tags":["migration","sqlite","tombstone","sync"],"backgroundTag":"legacy-tombstone-row","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}