Skip to content

SDK: typed PmClient fields apply CLI sentinels ('-' reads stdin, 'none' clears), silently corrupting imported data #1614

Closed

Description

@unbraind

Problem

The typed SDK applies the CLI's input sentinels to literal values. An integration that writes provider data through PmClient silently loses or replaces data whenever a provider value happens to equal a sentinel. Reproduced with @unbrained/pm-cli 2026.10.27, Node 24.19, stdin closed:

Typed SDK call Stored result
update(id, { description: '-' }) description becomes "" (stdin was read)
update(id, { assignee: 'none' }) assignee is cleared (null)
update(id, { title: '-' }) title stays "-" (literal; inconsistent with description)

On the CLI, --description - meaning "read stdin" and --assignee none meaning "clear" are reasonable. Typed SDK fields carry data, though, and the caller already has a dedicated way to express "clear" (null / an explicit unset). A Linear issue whose body is -, or a Jira user literally named none, is rewritten on import with no warning. Downstream preview/conflict code that hashes the provider value then reports false conflicts forever (found while reviewing pm-linear field-ownership previews, unbraind/pm-linear#183).

Repro

import { PmClient } from '@unbrained/pm-cli/sdk';
const c = new PmClient({ pmRoot: `${process.cwd()}/.agents/pm`, author: 'probe' });
const { item } = await c.create({ type: 'Task', title: 't', description: 'd', assignee: 'alice' });
await c.update(item.id, { description: '-' });   // stdin consumed; description -> ""
await c.update(item.id, { assignee: 'none' });    // assignee cleared
console.log((await c.get(item.id)).item);

(Run with < /dev/null. Under a pipeline that has stdin, the description silently becomes whatever is on stdin.)

Expected

  • SDK option values are literal. Sentinels (- for stdin, none/empty for clear, @file) exist only in CLI argument parsing.
  • The SDK expresses clearing with null or a typed unset option, never with a magic string.
  • If keeping compatibility requires an opt-in, use something like new PmClient({ cliSentinels: true }). The default must be literal, and an SDK call should never read process stdin implicitly.
  • Add a conformance test: every string field round-trips every CLI sentinel byte-for-byte through create/update/append, and no SDK call reads stdin.

Activity

  1. unbraind commented on Oct 27, 2026

    @unbraind
    OwnerAuthor

    Tracked in pm-gh1614. The current-source sandbox reproduces both reported rewrites. The item covers literal SDK input boundaries and explicit CLI transport conformance.

  2. unbraind commented on Oct 28, 2026

    @unbraind
    OwnerAuthor

    New published-artifact verification: this remains reproducible in official @unbrained/pm-cli@2026.10.28 (npm published 2026-10-28T05:03:32.849Z; tarball SHA1 67ffc73776eb3fa8d09c9922deb89e03c5fd7618; release v2026.10.28, tag target b5241ced02ad3cf6a83b684db4f186ebf8c51397).

    Using the shipped bundled SDK in a fresh minimal synthetic tracker with stdin closed, the same sequence from the issue still yields description "" after {description:'-'}, cleared assignee after {assignee:'none'}, and literal title "-" after {title:'-'}. No provider data or live integrations were used. Thus the published release should not yet be treated as literal-value-safe for SDK importers. Expected behavior and regression criteria remain those in this issue; this is new release evidence, not a distinct defect.

    PR #1616 was still unmerged at this check, so its proposed fix was not assumed included. Coverage is this bounded three-field reproduction, not a complete sentinel-field conformance certification.

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