Skip to content

TL-572: linear CLI: 'issues create' (and 'update') never return applied labels, only 'issues get' does - #7

Closed
FelixLisczyk wants to merge 3 commits into
mainfrom
tl-572
Closed

TL-572: linear CLI: 'issues create' (and 'update') never return applied labels, only 'issues get' does#7
FelixLisczyk wants to merge 3 commits into
mainfrom
tl-572

Conversation

@FelixLisczyk

Copy link
Copy Markdown
Owner

No description provided.

FelixLisczyk and others added 3 commits August 28, 2026 14:51
The issueCreate mutation sent labelIds correctly, so Linear applied the
labels, but the mutation's selection set never asked for `labels` back.
The response therefore always carried `"labels": null`, even on a fully
successful create. A scripted or agentic caller reading the create
response to confirm label application got a false negative and had to
pay an extra `issues get` round-trip to find out the labels were there
all along.

Two defects were in play, and both are fixed here:

1. The selection set omitted `labels`. It also omitted `priority`,
   `estimate`, `dueDate`, and `cycle` — every one of them a value the
   caller passes *into* create, so every one of them carried the
   identical "I set it, the response says null, did it work?" failure.
   All five are now requested, copying the shapes from GetIssue verbatim
   so the two selection sets read as siblings. `delegate` and
   `attachments` are deliberately left out: issueCreate cannot set a
   delegate, and a new issue has no attachments, so requesting them
   could never surface caller-supplied data.

2. The DTO layer rendered an empty collection as `null`, which is
   ambiguous between "this issue has none" and "this renderer does not
   report them" — exactly the confusion above. populateIssueBase now
   always allocates, so `labels`, `children`, and `attachments` render
   as `[]`. Delegate deliberately keeps `omitempty` and still vanishes
   when nil; an absent object is unambiguous in a way an absent
   collection is not, and the comment on populateIssueBase records that
   so the two are not later "harmonised" back together.

This changes read-path JSON as well: `issues get|list` and `search` at
detailed|full report `[]` where they reported `null`. Confining it to
create would mean duplicating the DTO layer and making create's output
structurally diverge from get's, which undercuts the parity this fix is
for. It is a real if small break for consumers using has(), == null, or
// defaults, rather than a cosmetic one.

Tests are layered because no single layer covers this. The service-level
tests build their own core.Issue via the mock, so they prove the
DTO/formatter path but would stay green if the selection set regressed.
The new httptest-backed client test captures the outgoing mutation body
and is the only guard that fails on that regression — verified by
removing the fields and watching it go red. Its assertions are anchored
to whole lines, since a bare `labels` substring also matches `labelIds`
in the mutation input and would pass against the unfixed code.
The previous commit made labels, children, and attachments render as an
empty array rather than null. Comments were left rendering as null,
which would emit `"labels": []` next to `"comments": null` in the same
JSON object — reproducing, one field over, the exact ambiguity that
commit set out to remove.

Kept deliberately separate so it can be reverted on its own without
disturbing the labels fix, since it touches a field the create defect
never involved.

One acknowledged asymmetry: create does not request comments at all, so
a newly created issue now reports `"comments": []` for something the
server was never asked about. That is the accepted cost of a uniform
contract — the DTO describes the shape it renders, not the wire
response it was built from — and the test records it.
The end-to-end create test claimed to cover the GraphQL selection set, but
its fake server ignored the request and answered with a canned body that
carried labels regardless — so deleting the selection set left it green.
Its handler now asserts the captured mutation actually asks for labels.

Also cover parent in the field-survival test, and anchor the LabelDTO and
delegate leak assertions to their own fields instead of substring-scanning
the whole document, so they cannot start failing on unrelated content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBkx8wtrq2Mtb8N7jNerwZ
@FelixLisczyk

Copy link
Copy Markdown
Owner Author

Superseded by the upstream PR: joa23#74

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant