📋 Pre-flight Checks
📝 Bug Description
engram conflicts scan --dry-run --semantic writes to the database. It inserted 13 relation rows and mutated 4 pre-existing pending relations, while reporting dry_run: true and inserted: 0.
The inserted counter appears to track only the structural pass. The --semantic pass persists its verdicts regardless of --dry-run, and its writes are neither counted nor prevented.
This makes the flag actively misleading: an operator running --dry-run on a large store to preview the blast radius will mutate the relation graph instead. There is no conflicts delete subcommand, so the rows cannot be reverted through the CLI.
🔄 Steps to Reproduce
- Use a project with existing observations and at least one unjudged candidate pair.
- Record the baseline:
engram conflicts stats --project <P>
- Run the structural dry-run and confirm it is inert:
engram conflicts scan --project <P> --dry-run
- Run the semantic dry-run:
export ENGRAM_AGENT_CLI=claude
engram conflicts scan --project <P> --dry-run --semantic \
--max-semantic 34 --concurrency 3 --timeout-per-call 60 --yes
- Compare against the baseline:
engram conflicts stats --project <P>
engram conflicts list --project <P> --limit 200
✅ Expected Behavior
With --dry-run, no rows are inserted, updated, or transitioned out of pending, in either the structural or the semantic pass. Ideally the dry-run also prints the verdicts it would persist, so the preview has value on its own. Today the verdicts are only observable because they were written.
❌ Actual Behavior
The semantic pass persisted its verdicts. The command reported the opposite.
Command output:
Conflicts Scan (project: docs)
inspected: 452
candidates_found: 34
already_related: 0
inserted: 0
dry_run: true
semantic_judged: 21
semantic_skipped: 13
semantic_errors: 0
conflicts stats before:
By judgment_status:
pending: 70
judged: 57
By relation type:
not_conflict: 55
pending: 70
related: 2
conflicts stats after (same command, nothing else run in between):
By judgment_status:
pending: 66
judged: 74
By relation type:
compatible: 4
not_conflict: 54
pending: 66
related: 13
scoped: 3
Total relations went from 127 to 140. Relation types compatible and scoped did not exist in this project before the run. pending dropped by 4, so existing rows were transitioned, not just appended.
The 13 new rows are ids 288–300, all with created_at between 01:37:22 and 01:39:15 UTC — the exact window of the scan:
id=300 rel=scoped status=judged created=2026-07-10 01:39:15
id=299 rel=related status=judged created=2026-07-10 01:39:04
id=298 rel=compatible status=judged created=2026-07-10 01:38:51
...
id=288 rel=related status=judged created=2026-07-10 01:37:22
Observations were not affected — only the relation graph.
Note: 21 pairs were judged but only 13 rows appeared. The gap is consistent with #490 (not_conflict verdicts persist nothing), so the 8 missing rows are likely not_conflict. That is a separate bug; it is mentioned only because it makes the row delta look smaller than the number of writes actually attempted.
Operating System
Linux (Ubuntu/Debian)
Engram Version
1.19.0
Agent / Client
Claude Code
Shell
bash
📋 Relevant Logs
$ engram conflicts show 300
Conflict Detail
relation_id: 300
sync_id: rel-0ded0339935af39a
relation: scoped
judgment_status: judged
created_at: 2026-07-10 01:39:15
updated_at: 2026-07-10 01:39:15
source_id: obs-8f839d61c753db4d
source_title: sdd/auditoria-cargas/apply-progress
target_id: obs-3228c1d6c8c7ed05
target_title: sdd/auditoria-cargas/tasks
💡 Additional Context
Two things make this costlier than a normal flag bug:
-
--semantic spends model tokens. It requires ENGRAM_AGENT_CLI and spawns one headless agent process per candidate pair. The run above issued 21 model calls. An operator reaching for --dry-run to estimate that cost before committing will pay the cost and take the writes.
-
The writes are not reversible from the CLI. engram conflicts exposes list, show, stats, scan, and deferred — there is no delete.
Suggested fixes, in order of value:
- Thread the dry-run flag into the semantic pass so it evaluates and reports without persisting.
- Have the dry-run print the verdicts it would write (
source, target, relation), which is the output an operator actually wants from a preview.
- Make
inserted count every row the command would touch, including semantic writes, so the counter cannot disagree with the store.
- Consider
conflicts delete <relation_id> for recovery.
📋 Pre-flight Checks
status:approvedbefore a PR can be opened📝 Bug Description
engram conflicts scan --dry-run --semanticwrites to the database. It inserted 13 relation rows and mutated 4 pre-existingpendingrelations, while reportingdry_run: trueandinserted: 0.The
insertedcounter appears to track only the structural pass. The--semanticpass persists its verdicts regardless of--dry-run, and its writes are neither counted nor prevented.This makes the flag actively misleading: an operator running
--dry-runon a large store to preview the blast radius will mutate the relation graph instead. There is noconflicts deletesubcommand, so the rows cannot be reverted through the CLI.🔄 Steps to Reproduce
✅ Expected Behavior
With
--dry-run, no rows are inserted, updated, or transitioned out ofpending, in either the structural or the semantic pass. Ideally the dry-run also prints the verdicts it would persist, so the preview has value on its own. Today the verdicts are only observable because they were written.❌ Actual Behavior
The semantic pass persisted its verdicts. The command reported the opposite.
Command output:
conflicts statsbefore:conflicts statsafter (same command, nothing else run in between):Total relations went from 127 to 140. Relation types
compatibleandscopeddid not exist in this project before the run.pendingdropped by 4, so existing rows were transitioned, not just appended.The 13 new rows are ids 288–300, all with
created_atbetween01:37:22and01:39:15UTC — the exact window of the scan:Observations were not affected — only the relation graph.
Note: 21 pairs were judged but only 13 rows appeared. The gap is consistent with #490 (
not_conflictverdicts persist nothing), so the 8 missing rows are likelynot_conflict. That is a separate bug; it is mentioned only because it makes the row delta look smaller than the number of writes actually attempted.Operating System
Linux (Ubuntu/Debian)
Engram Version
1.19.0
Agent / Client
Claude Code
Shell
bash
📋 Relevant Logs
💡 Additional Context
Two things make this costlier than a normal flag bug:
--semanticspends model tokens. It requiresENGRAM_AGENT_CLIand spawns one headless agent process per candidate pair. The run above issued 21 model calls. An operator reaching for--dry-runto estimate that cost before committing will pay the cost and take the writes.The writes are not reversible from the CLI.
engram conflictsexposeslist,show,stats,scan, anddeferred— there is nodelete.Suggested fixes, in order of value:
source,target,relation), which is the output an operator actually wants from a preview.insertedcount every row the command would touch, including semantic writes, so the counter cannot disagree with the store.conflicts delete <relation_id>for recovery.