Skip to content

SDK: parseItemDocument throws PmCliError with an empty context and no error_code, so consumers can only tell failure modes apart by string-matching English prose #691

Closed

Description

@unbraind

Summary

parseItemDocument (exported from @unbrained/pm-cli/sdk) rejects every invalid document with a PmCliError whose context is {} and which carries no code / error_code. The only machine-readable signal is exitCode: 1, which is the same for all failures.

The CLI elsewhere emits structured, stable error codes (schema_migration_input_required, extension_item_field_unknown, close_reason_required, …). The SDK's parse path does not, so a programmatic consumer has to match on prose that is free to change in any release.

Reproduction

import { parseItemDocument } from "@unbrained/pm-cli/sdk";

const cases = {
  "missing description": 'id: a\ntitle: t\n',
  "missing priority":    'id: a\ntitle: t\ndescription: d\ntype: Issue\nstatus: open\ncreated_at: "2026-01-01T00:00:00.000Z"\nupdated_at: "2026-01-01T00:00:00.000Z"\n',
  "malformed syntax":    "  not: [a valid: toon",
};

for (const [name, src] of Object.entries(cases)) {
  try { parseItemDocument(src); }
  catch (e) { console.log(name, "|", e.constructor.name, "| code:", e.code ?? "(none)", "| context:", JSON.stringify(e.context)); }
}

Observed on @unbrained/pm-cli@2026.7.24:

missing description  | PmCliError | code: (none) | context: {}
missing priority     | PmCliError | code: (none) | context: {}
malformed syntax     | PmCliError | code: (none) | context: {}

Two distinct problems

1. No structured discriminator. All three failures are indistinguishable except by message text. A consumer that wants to treat "this file is not a pm item at all" differently from "this is a pm item written by a newer schema" cannot.

2. A misleading diagnostic for unparseable input. The malformed syntax case — input that is not a valid document at all — reports:

Invalid item metadata: id is required and must be a string

That points the reader at a missing field when the real problem is that the document never parsed. For a human debugging a hand-edited item, this actively sends them the wrong way.

Why it matters

Any SDK consumer reading item files it did not write hits this. In our case it is a crawler indexing pm workspaces from public repositories: when a repository indexes short, the operator needs to know why — a file that is genuinely corrupt is a different situation from a file written by a schema version this build predates, and only the second is a reason to upgrade. Today both are reported identically and the honest thing to publish is just "unparseable", which is less than the SDK actually knows.

The same applies to pm-github, pm-csv and pm-jira, which all parse item documents they did not author.

Suggested shape

Populate the existing PmCliError.context and add a stable code, following the convention the CLI already uses:

{
  code: "item_document_invalid",        // or item_document_unparseable
  context: {
    reason: "missing_required_field",   // | "syntax_error" | "unknown_schema_version"
    field: "description",               // when reason is missing_required_field
    format_version: 2                   // when reason is unknown_schema_version
  }
}

Distinguishing syntax_error from missing_required_field also fixes problem 2 on its own: input that fails to parse would stop reporting a specific missing field it never got far enough to check.

No behaviour change is required — this is purely additive on an error object consumers already receive.

Environment

  • @unbrained/pm-cli@2026.7.24
  • Node v26.5.0, Linux

Activity

  1. coderabbitai commented on Jul 25, 2026

    @coderabbitai
    🔗 Related PRs

    #418 - Refactor residual CodeFactor complexity hotspots [merged]
    #549 - feat(sdk): ship history time travel, rich reads, and runtime integrity [merged]
    #559 - SDK durable relationship history and bounded context adapters [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 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 25, 2026

    @unbraind
    OwnerAuthor

    Tracked by pm item pm-r9pudt. This item remains open and unclaimed because it was documented during the current broad delivery but is not part of the actively edited scope.

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