{"record":{"id":"70e8ee57d495dd6d","repo":"gastownhall/beads","slug":"inspect-dirty-config-before-pull-w","errorCode":null,"errorMessage":"inspect dirty config before pull: %w","messagePattern":"inspect dirty config before pull: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/store.go","lineNumber":3185,"sourceCode":"\n// assertDirtyConfigUserKVOnly returns an error unless every config row dirty in\n// the working set is this clone's own user KV data (the kv.* namespace, which\n// includes kv.memory.* memories). The pre-pull auto-commit opts config into the\n// staged set so user KV writes sync and stop wedging DOLT_MERGE (GH#2474), but\n// auto-committing an unrelated dirty internal config key such as issue_prefix\n// would re-open the GH#2455 stale-config corruption — that is the operator's\n// explicit `bd dolt commit` to make, not the pull's. Screening on the whole kv.\n// namespace (not just kv.memory.*) un-wedges generic `bd kv set` writes too: a\n// kv.* row is this clone's own data, exactly as safe to auto-commit as a memory,\n// and a genuine kv.* merge conflict is still left for the operator because\n// auto-resolution stays kv.memory.*-only (configConflictsAreMemoryConvergent).\n// config's primary key is `key`, so dolt_diff exposes to_key/from_key; an add or\n// delete leaves one side NULL, so COALESCE picks whichever key the change carries.\nfunc (s *DoltStore) assertDirtyConfigUserKVOnly(ctx context.Context, conn *sql.Conn) error {\n\trows, err := conn.QueryContext(ctx,\n\t\t\"SELECT COALESCE(to_key, from_key) FROM dolt_diff('HEAD', 'WORKING', 'config')\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"inspect dirty config before pull: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tvar unsafe []string\n\tfor rows.Next() {\n\t\tvar key sql.NullString\n\t\tif err := rows.Scan(&key); err != nil {\n\t\t\treturn fmt.Errorf(\"scan dirty config key: %w\", err)\n\t\t}\n\t\tif key.Valid && !strings.HasPrefix(key.String, kvkeys.Prefix) {\n\t\t\tunsafe = append(unsafe, key.String)\n\t\t}\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn fmt.Errorf(\"iterate dirty config diff: %w\", err)\n\t}\n\tif len(unsafe) > 0 {\n\t\treturn fmt.Errorf(\"refusing to auto-commit %d dirty internal config key(s) before pull: %s; \"+","sourceCodeStart":3167,"sourceCodeEnd":3203,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/store.go#L3167-L3203","documentation":"Before concluding a pull/merge, assertDirtyConfigUserKVOnly inspects dolt_diff('HEAD','WORKING','config') to ensure any dirty config rows are internal kv.* keys only (GH#2474). This error wraps a failure of the dolt_diff query itself, meaning the safety check could not run, so the merge is refused before staging. It is a protective abort, not corruption.","triggerScenarios":"The QueryContext on dolt_diff fails: Dolt version lacking the dolt_diff table-function form used here, invalid arguments (bad 'HEAD'/'WORKING' refs or table name), session error on the pinned connection, or transient driver failure.","commonSituations":"Older Dolt engines with a different dolt_diff signature; a pull attempted while the working set/HEAD ref is in an unexpected state (mid-merge, detached session); network blip to a remote server.","solutions":["Check the wrapped error; retry the pull on a fresh pinned connection if transient.","Verify the Dolt engine version supports dolt_diff('HEAD','WORKING',table) table-function syntax; upgrade if not.","Inspect merge state (dolt_merge_status) — an open or broken merge can invalidate WORKING diffing; resolve or abort the merge first.","Manually run SELECT ... FROM dolt_diff('HEAD','WORKING','config') in a SQL shell to reproduce and see the server-side message."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// precheck: engine supports the table-function form and clean merge state\n_, err := conn.QueryContext(ctx, \"SELECT 1 FROM dolt_diff('HEAD', 'WORKING', 'config') LIMIT 1\")\nif err != nil { /* resolve engine version / merge state before pull */ }","typeGuard":null,"tryCatchPattern":"err := store.Pull(ctx, ...)\nif err != nil && strings.Contains(err.Error(), \"inspect dirty config before pull\") {\n\t// check dolt_merge_status, resolve open merge, retry pull\n}","preventionTips":["Conclude or abort open merges before pulling (dolt_merge_status).","Keep the Dolt engine version supporting dolt_diff('HEAD','WORKING',table).","Keep internal config keys under the kv.* prefix so the safety check can pass.","Retry pulls on a fresh connection after transient SQL errors."],"tags":["go","dolt","diff","config","pull"],"backgroundTag":"dolt-diff-config-inspection-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}