Skip to content

Add, remove, and set flags for project teams, labels, and initiatives - #282

Open
schpetbot wants to merge 1 commit into
mainfrom
schpet/project-collection-flags
Open

Add, remove, and set flags for project teams, labels, and initiatives#282
schpetbot wants to merge 1 commit into
mainfrom
schpet/project-collection-flags

Conversation

@schpetbot

@schpetbot schpetbot commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

First of a two-PR stack (this → #283), continuing the MCP-informed series (#279, #280, #281).

project update now offers the same three operations for every collection it can change, mirroring the label triad issue update already had: --team/--label/--initiative replace the set, --add-* appends, --remove-* detaches, all repeatable, with replace rejected alongside add or remove and the same item rejected in both add and remove. project update previously could not change initiatives at all.

Implementation notes worth knowing: ProjectUpdateInput only has replace-style teamIds/labelIds, so add and remove read the current connection with cursor pagination, compute the full set, and send it. Removing an absent item errors and lists the current set; removing the last team errors because Linear rejects an empty team set (verified live). Initiatives have no input field, so the flags diff against project.initiativeToProjects and run initiativeToProjectDelete before initiativeToProjectCreate (delete first because the schema forbids a project on both an initiative and its ancestor or descendant). UUID-shaped initiatives are confirmed to exist before any link is touched, and a partial failure reports applied, unknown, and not-applied changes with retry flags by UUID.

Verified: 747 tests in both color modes, a 21-case QA table run live on scratch objects (all deleted), and four high-effort Codex review passes whose real findings (link ordering against the initiative hierarchy, hidden already-applied update, unvalidated UUIDs, non-unique retry flags, non-advancing cursor, unknown outcome on a thrown request) are all fixed with tests.

https://claude.ai/code/session_01A9qEGri4p2HZMQSuYsBmub

project update could only replace a project's teams and labels, and could
not change initiatives at all: project create accepts --initiative, but
after creation the only route was a hand-written mutation. Every
collection field an update command can change should offer the same three
operations, following the pattern issue update already has for labels:
--team/--label/--initiative replace the whole set, --add-* appends,
--remove-* detaches, all repeatable, and a replace flag cannot be combined
with its add/remove flags.

Linear's ProjectUpdateInput only takes a full teamIds/labelIds list, so
add and remove read the project's current connection, following every
page, compute the new set, and send it. Removing something the project
does not have is an error that lists what it does have, rather than a
silent no-op, and removing the last team errors up front because Linear
rejects an empty teamIds. Initiatives have no input field: membership is
an InitiativeToProject join row, so all three initiative flags diff the
desired set against the current rows and run initiativeToProjectDelete
then initiativeToProjectCreate. Deletes go first because a project may
appear only once in an initiative hierarchy, so moving it to a parent or
child initiative is rejected while the old link still exists; that is
also why a UUID-shaped initiative is confirmed to exist before any link
is touched, since the shared resolver passes UUIDs through unchecked.
Those mutations are not transactional, so a failure part-way reports what
was applied, including the already-committed project fields, and what is
still pending, with the remaining flags given by initiative ID because
names are not unique, instead of implying a rollback.

The survey of issue update and initiative update found no other
replace-only collection flag: IssueUpdateInput's releaseIds and
subscriberIds and InitiativeUpdateInput's labelIds are not exposed by
either command.

Claude-Session: https://claude.ai/code/session_01A9qEGri4p2HZMQSuYsBmub
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