Repository navigation
Description
Observed
Probed against pm-cli 2026.8.3 in a throwaway workspace:
$ pm create --title "envelope probe" --type Task --json
{"id":"pm-gqpv","status":"open","changed_field_count":10}
$ pm update pm-ty5w --priority 1 --json
{"id":"pm-ty5w","status":"open","changed_field_count":1}
$ pm close pm-ty5w --reason "probe done" --json
{"id":"pm-ty5w","status":"closed","changed_field_count":4,"close_reason":"probe done"}
$ pm read pm-ty5w --json
{"item":{"id":"pm-ty5w","title":"envelope probe", ...}}
$ pm list --json
{"items":[{"id":"pm-ty5w", ...}]}So mutations return a flat receipt ({id, status, changed_field_count}) while queries return a wrapped entity ({item: …} / {items: […]}).
That split is defensible as a design — a mutation receipt genuinely is not an entity. The problem is that it is undiscoverable: nothing in --help states it, and the two shapes are similar enough that an author who has seen pm read --json will reasonably assume pm create --json wraps too.
Why I am filing it
This is not hypothetical. It shipped a silent production bug in one of our extensions.
An importer needed the id of a freshly created item in order to run the follow-up pm close --reason (required since 2026.8.3 — see #887). It parsed the create output as:
const id = parsed?.item?.id; // undefined, alwaysBecause the parser returns undefined rather than throwing, nothing failed: pm create exited 0, the id was empty, the follow-up close was skipped, and every imported issue that was closed upstream landed open while the import reported success. The same undefined also silently disabled comment syncing. It went unnoticed through review and a full test suite, because the unit test asserted the same fabricated {item:{id}} shape the parser expected — test and parser agreed with each other rather than with the CLI.
The class of bug matters more than our specific instance: any consumer that guesses this shape wrong gets silence, not an error.
Requests (in preference order)
-
Document the contract explicitly. State in
--jsonhelp (and the docs) that mutation commands emit a flat receipt and query commands emit a wrapped entity, and thatidis top-level on mutations. A one-line statement would have prevented this entirely. -
Ship a typed SDK helper so extensions never hand-parse a receipt:
import { parseMutationReceipt } from "@unbrained/pm-cli/sdk"; const { id, status, changedFieldCount } = parseMutationReceipt(stdout);
This is the highest-value fix for agent authors: today every extension that needs a created id reimplements the same fragile parse. The SDK already owns the shape, so it should own the parser.
-
Consider a machine-readable discriminant on
--jsonoutput — e.g. akind: "receipt" | "item" | "items"field — so a consumer can assert what it received instead of guessing. This would let a wrong assumption fail loudly at the boundary rather than silently downstream.
I am not asking you to unify the two shapes; changing them now would break every existing consumer. Documenting them and giving the SDK a parser gets the safety without the breakage.
Context
Found while upgrading a 20-package extension fleet to 2026.8.3. Related: #887 (no single-command way to create an item in a terminal state, which is why the importer needed to parse the created id in the first place — fixing that would remove this hazard for the whole importer class).
Activity
coderabbitai commentedon Aug 3, 2026
🔗 Related PRs
#549 - feat(sdk): ship history time travel, rich reads, and runtime integrity [merged]
#593 - SDK-first agent UX graph, ownership, and recovery contracts [merged]
#617 - feat(sdk): harden multi-agent identity, merge fences, and typed mutations [merged]
#624 - SDK atomic structured mutations, lean output, and agent-first envelopes [merged]
#673 - feat(sdk): harden workspace mutation and migration primitives [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 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:💬 Have feedback or questions? Drop into our discord!
Drop the dead item-wrapper fallback from the create envelope parse
Route terminal CSV row imports through pm close for pm-cli 2026.8.3 (#57
unbraind commentedon Aug 4, 2026
Tracked in the existing uniform output-contract owner pm-srns.