Skip to content

Agent UX: duplicate detection is create-time only — no CLI command reports duplicates that already exist, though the SDK's findDuplicateClusters returns them instantly #739

Closed

Description

@unbraind

Summary

governance.duplicate_detection_mode=advisory warns when a new item resembles an existing one. There is no CLI surface that answers the complementary question: "which duplicates does this workspace already contain?"

That matters because the advisory only ever fires at create time. Any workspace that accumulated duplicates before the mode was enabled — which is every workspace initialized with the minimal governance preset, since it defaults to off — carries those duplicates permanently invisible to the CLI.

The capability is not missing from the codebase. findDuplicateClusters is exported from @unbrained/pm-cli/sdk/governance and returns fully-formed clusters immediately. It simply has no command in front of it.

Reproduction

mkdir /tmp/dup && cd /tmp/dup && pm init tst

# minimal preset => duplicate_detection_mode defaults to "off"
pm config get governance.duplicate_detection_mode --json | grep '"value"'
#   "value": "off"

# create three identical items — no warning of any kind
for i in 1 2 3; do pm create --title "Implement OAuth login flow for the web dashboard" --type Feature; done

# turn the advisory on; a NEW duplicate is correctly flagged
pm config set governance.duplicate_detection_mode advisory
pm create --title "Implement OAuth login flow for the web dashboard" --type Feature
#   warnings:
#     - "likely_duplicates:tst-cn9f,tst-dlwo,tst-z81x"     <-- works

# ...but nothing reports the four duplicates now sitting in the workspace:
for c in health validate stats aggregate context next; do
  echo "pm $c -> $(pm $c 2>&1 | grep -ciE 'likely_dup|duplicate_cluster|similar_to')"
done
#   pm health -> 0
#   pm validate -> 0
#   pm stats -> 0
#   pm aggregate -> 0
#   pm context -> 0
#   pm next -> 0

Note that pm validate does emit a duplicate_issue_codes_count field, which is a different concept (duplicate issue codes, not duplicate items) and is easy to mistake for coverage of this.

Meanwhile, against that same workspace:

const g = await import('@unbrained/pm-cli/sdk/governance');
const r = await g.findDuplicateClusters({ pmRoot: '/tmp/dup/.agents/pm' });
// {"clusters":[{"id":"tst-cn9f","items":[
//   {"id":"tst-cn9f","title":"Implement OAuth login flow for the web dashboard","status":"open","type":"Feature"},
//   {"id":"tst-dlwo",...},{"id":"tst-z81x",...}],"matches":[...]}]}

Complete, structured, and already tested — just unreachable from the CLI.

Why this matters for agents specifically

An agent starting a session on an unfamiliar workspace cannot establish whether the backlog is clean. The failure is silent and compounding: the agent picks up tst-cn9f, does the work, closes it, and the two identical siblings stay open — so the next agent redoes the same work. Nothing in pm context or pm next (the two commands an agent actually runs at session start) hints that the item it just selected has twins.

This is the reverse of how the rest of the governance surface behaves: pm health and pm validate are backward-looking audits over existing state, and duplicates are conspicuously the one governance signal that has no backward-looking mode.

Suggested fixes

  1. Add duplicate clusters to pm health (or pm validate) as an advisory check, gated on governance.duplicate_detection_mode != off so it stays opt-in and costs nothing for workspaces that do not want it. This is the smallest change and puts the signal where agents already look for workspace-quality problems.

  2. Or add an explicit command — pm duplicates / pm graph duplicates — wrapping findDuplicateClusters with --threshold / --limit (the config keys governance.duplicate_detection_threshold and ..._limit already exist) and clean --json output.

  3. Either way, emit the resolving command per cluster. A cluster is only actionable if the agent is told how to collapse it; printing cluster members without the follow-up command forces another roundtrip to work out the right pm close/merge invocation.

Adding a duplicate_clusters count to pm context would close the loop for the session-start case, though I would understand keeping that out of the default context budget and behind a flag.

Environment

  • @unbrained/pm-cli 2026.7.26 (global install, npm)
  • Node 22, Linux
  • Fresh pm init workspace, governance.preset = minimal

Context

Found while auditing SDK adoption across the 18-package pm-* extension fleet — sdk/governance had zero adopters across the fleet despite exporting exactly the scanners several packages needed. Related to #715, which is the same shape of problem on a different governance surface (mutation_guard block mode being reachable in config but undiscoverable from --help).

Activity

  1. coderabbitai commented on Jul 26, 2026

    @coderabbitai
    🔗 Related PRs

    #581 - feat(sdk): graph governance completeness, dry-run remediation plans, incremental graph cache, MCP nested-option warnings [merged]
    #593 - SDK-first agent UX graph, ownership, and recovery contracts [merged]
    #622 - feat(graph): durable indexes, governance baselines, and mutation advisories [merged]
    #673 - feat(sdk): harden workspace mutation and migration primitives [merged]
    #684 - SDK context coordination: mutation events, duplicate governance, and startup budgets [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. unbraind commented on Jul 26, 2026

    @unbraind
    OwnerAuthor

    Follow-up data point, offered as evidence for the feature rather than as a complaint: I ran findDuplicateClusters across all 19 pm workspaces I have locally, including pm-cli's own .agents/pm.

    The 18 extension workspaces are essentially clean — 15 fully clean, 3 with a single all-closed cluster each. pm-cli's own workspace has 26 clusters, and this is the part that makes the case:

    total clusters: 26
    clusters where EVERY member is open: 6
    open items inside those: 29
    clusters with >=1 open member: 9
    
    largest all-open clusters:
      10x  "Semgrep 874840464: Medium non-literal RegExp in src/cli/commands/test.ts#L1506"
       6x  "Semgrep 874840479: Medium non-literal RegExp in src/cli/commands/linked-test-p…"
       5x  "Semgrep 874840473: Medium non-literal RegExp in src/cli/commands/search.ts#L13…"
       4x  "Semgrep 874840456: Medium non-literal RegExp in src/cli/commands/validate.ts#L…"
    

    Ten open items for one Semgrep finding, six for another — 29 open duplicates in total, presumably from an ingestion job that re-filed on each run without a dedupe check.

    This is exactly the failure mode described above, and it illustrates the point better than my synthetic repro: the duplicates are invisible to pm health, pm validate, pm stats, pm context, and pm next, so nothing surfaces them during normal use — but a single SDK call enumerates all of them in well under a second. An agent running pm next in this workspace can be handed one of ten identical items with no indication the other nine exist.

    It also suggests the create-time advisory alone would not have prevented this: a bulk importer that constructs items programmatically is precisely the path most likely to run with duplicate_detection_mode=off, which is the minimal preset default. That is an argument for the backward-looking scan (option 1 or 2 above) being the more valuable half of the fix.

    Happy to open a separate housekeeping issue for the 29 items themselves if that is useful — I did not want to conflate workspace cleanup with the CLI gap.

  3. unbraind commented on Jul 26, 2026

    @unbraind
    OwnerAuthor

    Tracked by pm-n13lzc, linked to the shipped batch duplicate-cluster primitive, create-time advisory, and dedupe-merge lineage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions