Skip to content

Merge receipts store item_path with literal surrounding quotes from %P, and pm merge reconcile publishes the quoted path into committed history #771

Closed

Description

@unbraind

Summary

Merge decision receipts store item_path with literal single-quote characters embedded in the string, because the value is taken verbatim from git's %P and the .gitattributes patterns that pm merge install writes are themselves quoted. The bad value is not confined to the clone-local receipt — pm merge reconcile copies it into the committed history/<id>.jsonl audit entry, so it is published to every consumer of the tracker.

Reproduction (pm 2026.7.27)

git init -q lab && cd lab
git config user.email a@b.c && git config user.name t
export PM_PATH="$PWD/.pm"
pm init lab --json >/dev/null && pm merge install --json >/dev/null
ID=$(pm create --title "Shared item" --type Task --json | node -pe 'JSON.parse(require("fs").readFileSync(0)).id')
git add -A && git commit -qm base && BASE=$(git rev-parse HEAD)
git checkout -qb agent-a && pm update "$ID" --description "A" --json >/dev/null && git add -A && git commit -qm a
git checkout -q "$BASE" && git checkout -qb agent-b && pm update "$ID" --description "B" --json >/dev/null && git add -A && git commit -qm b
git merge agent-a -m merge

pm merge report --json | grep item_path

Observed:

"item_path": "'.pm/tasks/lab-fx0u.toon'"

Expected:

"item_path": ".pm/tasks/lab-fx0u.toon"

The quotes are part of the string value (confirmed in the raw receipt file
.git/pm-merge-receipts/<uuid>.json, not a display artifact of the JSON encoder).

After pm merge reconcile, the same quoted value is persisted into the committed history entry:

{"op":"merge_reconcile", ..., "context":{"merge":{"receipts":[
   {"item_id":"lab-fx0u","item_path":"'.pm/tasks/lab-fx0u.toon'", ...}]}}}

Cause

pm merge install writes quoted patterns into .gitattributes (correct — the fence needs them for paths with spaces):

".pm/tasks/*.toon" merge=pm-item-toon

git then passes the matched path through to the driver's %P placeholder, and the driver stores the received --item-path string without stripping the surrounding quotes.

Impact

Any consumer that wants to act on a receipt — open the conflicted item, link it in a report, diff it, group receipts by directory — has to know to strip quotes first:

fs.readFileSync(receipt.item_path)                  // ENOENT
path.resolve(root, receipt.item_path)               // .../'.pm/tasks/lab-fx0u.toon'
receipt.item_path.replace(/^'|'$/g, "")             // the workaround every consumer needs

item_id is clean, so receipts remain usable via id lookup — this is a correctness/ergonomics bug rather than a blocker, but it is baked into published history and will need a migration if left.

Suggested fix

Strip a single matched layer of surrounding '/" from the --item-path value where the driver ingests it (before it reaches the receipt writer), so both the clone-local receipt and the committed merge_reconcile audit entry carry a plain repository-relative path. Existing receipts/history entries can be normalised on read for backward compatibility.

Environment

  • pm-cli 2026.7.27 (@unbrained/pm-cli), Node 26.5.0, Linux
  • .gitattributes fence written by pm merge install (unmodified)

Activity

  1. coderabbitai commented on Jul 27, 2026

    @coderabbitai
    ⚠️ Possible Duplicate Issue(s)
    🔗 Related PRs

    #317 - fix(ci): resolve Windows script-spec separator/mocked-path class (pm-s5pe) [merged]
    #475 - Improve package install and annotation agent UX [merged]
    #614 - SDK-first field-aware merge safety and storage integrity [merged]
    #673 - feat(sdk): harden workspace mutation and migration primitives [merged]
    #758 - Make branch merge decisions recoverable, replayable, and CI-enforced [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 27, 2026

    @unbraind
    OwnerAuthor

    Canonical PM owner: pm-9nfpwd, originally reported as #763 and fixed on main in PR #767. The tracked fix removes duplicate quoting and normalizes one legacy quote pair before receipts or reconciled history consume the path. This report is therefore linked to the existing all-status canonical item rather than creating a duplicate PM item; the currently published 2026.7.27 package predates that main-branch fix.\n\nCodeRabbit correctly identified #763 as the duplicate lineage.

  3. unbraind commented on Jul 28, 2026

    @unbraind
    OwnerAuthor

    Merged resolution: #767. Existing canonical PM item: pm-9nfpwd. Receipt and reconciled-history paths normalize one legacy surrounding quote pair; this report was the duplicate lineage of #763.

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