From 9dc0da9fddca1a06db956cbc7e3efc286f98ce8d Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Fri, 26 Dec 2025 00:45:59 -0800 Subject: [PATCH] docs: Document COALESCE/NULLIF clone-local field protection pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expands the inline comment to serve as a reference for when to use the COALESCE(NULLIF(?, zero), column) defensive idiom in JSONL imports. Pattern protects clone-local state (pinned, gate fields) from being overwritten when importing JSONL where omitempty causes zero values to represent "field absent" rather than "field is zero". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/storage/sqlite/multirepo.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/internal/storage/sqlite/multirepo.go b/internal/storage/sqlite/multirepo.go index 4d5b7045..34f37fdb 100644 --- a/internal/storage/sqlite/multirepo.go +++ b/internal/storage/sqlite/multirepo.go @@ -330,12 +330,23 @@ func (s *SQLiteStorage) upsertIssueInTx(ctx context.Context, tx *sql.Tx, issue * } if existingHash != issue.ContentHash { - // Pinned field fix (bd-phtv): Use COALESCE(NULLIF(?, 0), pinned) to preserve - // existing pinned=1 when incoming pinned=0 (which means field was absent in - // JSONL due to omitempty). This prevents auto-import from resetting pinned issues. - // Gate field fix (bd-gr4q): Same pattern for await_type, await_id, timeout_ns, waiters. - // Gates are wisps and aren't exported to JSONL, so importing an issue with the same - // ID would clear these fields without this protection. + // Clone-local field protection pattern (bd-phtv, bd-gr4q): + // + // Some fields are clone-local state that shouldn't be overwritten by JSONL import: + // - pinned: Local hook attachment (not synced between clones) + // - await_type, await_id, timeout_ns, waiters: Gate state (wisps, never exported) + // + // Problem: Go's omitempty causes zero values to be absent from JSONL. + // When importing, absent fields unmarshal as zero, which would overwrite local state. + // + // Solution: COALESCE(NULLIF(incoming, zero_value), existing_column) + // - For strings: COALESCE(NULLIF(?, ''), column) -- preserve if incoming is "" + // - For integers: COALESCE(NULLIF(?, 0), column) -- preserve if incoming is 0 + // + // When to use this pattern: + // 1. Field is clone-local (not part of shared issue ledger) + // 2. Field uses omitempty (so zero value means "absent", not "clear") + // 3. Accidental clearing would cause data loss or incorrect behavior _, err = tx.ExecContext(ctx, ` UPDATE issues SET content_hash = ?, title = ?, description = ?, design = ?,