Skip to content

Add issue update --unassign to clear an issue's assignee#249

Merged
schpetbot merged 1 commit into
mainfrom
schpet/push-yqmwkrxzzqyx
Jul 18, 2026
Merged

Add issue update --unassign to clear an issue's assignee#249
schpetbot merged 1 commit into
mainfrom
schpet/push-yqmwkrxzzqyx

Conversation

@schpetbot

Copy link
Copy Markdown
Collaborator

There was currently no way to unassign an issue.

Root cause

The mutation input was built as Record<string, string | number | string[] | undefined> — a type that structurally cannot hold null. So IssueUpdateInput.assigneeId could never be set to null, no matter what flag was added on top. Adding a flag alone would not have compiled.

Change

  • Swap the hand-rolled Record for the codegen'd IssueUpdateInput. project update already does this (project-update.ts:152), so this restores consistency rather than introducing a pattern. Type check confirmed zero fallout across every existing field assignment.
  • Add --unassign, which sends assigneeId: null.
  • Passing both --assignee and --unassign raises a ValidationError with a suggestion, rather than one silently winning. The guard runs before any network or VCS call.

No short alias

-U, --unassigned is already bound in three sibling commands (issue-query.ts:73, issue-mine.ts:118, issue-start.ts:25) as a read-side filter. Binding -u, --unassign would put a data-clearing mutation one shift-key from a harmless filter. Long-form only; an alias can be added later but never removed.

Verification

Linear silently ignores projectId: null elsewhere in this codebase (document-update.ts:214), so mocked tests alone could not prove this works. Verified against the real API on a throwaway issue: assignee cleared, issue then deleted.

Tests assert the wire payload by pinning the mock's input to an exact shape, which is load-bearing — the mock matcher compares nested objects by exact key count, so a regression to undefined drops the key and fails to match. Confirmed by deliberately reverting the implementation and watching the tests fail.

Follow-ups (not in this PR)

  • The same nullable-clear gap exists for --project, --cycle, --parent, and --milestone. The type migration here unblocks each as roughly a one-liner.
  • issue create still builds its input as a loose object rather than IssueCreateInput.
  • Generated skill docs are not reproducible across machines: the generator's wrap width comes from Deno.consoleSize(), so regenerating on a different terminal reflows unrelated files. Only the real --unassign line is included here to avoid that churn.

There was no way to unassign an issue. The mutation input was built as a
`Record<string, string | number | string[] | undefined>`, a type that
structurally cannot hold null, so `IssueUpdateInput.assigneeId` could never
be set to null no matter what flags were added.

Swap that hand-rolled Record for the codegen'd `IssueUpdateInput` — matching
what `project update` already does — and add an explicit `--unassign` flag.
Passing both --assignee and --unassign is a ValidationError rather than one
silently winning.

No short alias: `-U, --unassigned` is already bound in three sibling commands
as a read-side filter, and putting a data-clearing mutation one shift-key away
from a harmless filter invites accidents.

Verified against the real API that Linear honors `assigneeId: null` — worth
checking explicitly, since it silently ignores `projectId: null` elsewhere.
@schpetbot
schpetbot merged commit fb8887c into main Jul 18, 2026
15 checks passed
@schpetbot
schpetbot deleted the schpet/push-yqmwkrxzzqyx branch July 18, 2026 21:07
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.

2 participants