Summary
milestones create hard-fails and milestones update silently drops fields. Both stem from the same mistake in src/services/milestone-service.ts: the variables are passed wrapped in { input } (and { id, input }), but the ProjectMilestone GraphQL mutations declare flat variables ($projectId, $name, $description, $targetDate, $sortOrder) — not a single $input object. So the variables arrive undefined.
Contrast with project-service (createProject), where { input } is correct because the CreateProject mutation uses a single $input: ProjectCreateInput!. The milestone mutations are different (flat vars), so the same pattern was copied incorrectly.
Version: linearis 2026.4.9 (npm global).
Bug 1 — milestones create hard-fails
$ linearis milestones create "M1" --project <projectUUID> --description "x"
{ "error": "GraphQL request failed: Variable \"$projectId\" of required type \"String!\" was not provided." }
The mutation is mutation CreateProjectMilestone($projectId: String!, $name: String!, $description: String, $targetDate: TimelessDate), but createMilestone sends { input: { projectId, name, ... } }, so $projectId/$name are never bound.
Bug 2 — milestones update silently drops --description / --target-date / --sort-order
updateMilestone sends { id, input } against mutation UpdateProjectMilestone($id: String!, $name: String, $description: String, $targetDate: TimelessDate, $sortOrder: Float). $id binds (it's top-level), but $name/$description/$targetDate/$sortOrder are nested under input and never bind — so the update succeeds with no changes and no error. This is worse than Bug 1 because it's silent.
Root cause
src/services/milestone-service.ts (compiled dist/services/milestone-service.js):
// createMilestone
const result = await client.request(CreateProjectMilestoneDocument, { input }); // ❌ should be: input
// updateMilestone
const result = await client.request(UpdateProjectMilestoneDocument, { id, input }); // ❌ should be: { id, ...input }
Fix
export async function createMilestone(client, input) {
- const result = await client.request(CreateProjectMilestoneDocument, { input });
+ const result = await client.request(CreateProjectMilestoneDocument, input);
export async function updateMilestone(client, id, input) {
- const result = await client.request(UpdateProjectMilestoneDocument, { id, input });
+ const result = await client.request(UpdateProjectMilestoneDocument, { id, ...input });
Verified against the live Linear API: both create and update (with --description and --target-date) work after this change.
Suggested follow-up
A small integration test that asserts the variables object shape matches each mutation document's declared variables would catch this class of { input } vs flat-variable mismatch across all services.
Summary
milestones createhard-fails andmilestones updatesilently drops fields. Both stem from the same mistake insrc/services/milestone-service.ts: the variables are passed wrapped in{ input }(and{ id, input }), but the ProjectMilestone GraphQL mutations declare flat variables ($projectId,$name,$description,$targetDate,$sortOrder) — not a single$inputobject. So the variables arriveundefined.Contrast with
project-service(createProject), where{ input }is correct because theCreateProjectmutation uses a single$input: ProjectCreateInput!. The milestone mutations are different (flat vars), so the same pattern was copied incorrectly.Version: linearis 2026.4.9 (npm global).
Bug 1 —
milestones createhard-failsThe mutation is
mutation CreateProjectMilestone($projectId: String!, $name: String!, $description: String, $targetDate: TimelessDate), butcreateMilestonesends{ input: { projectId, name, ... } }, so$projectId/$nameare never bound.Bug 2 —
milestones updatesilently drops--description/--target-date/--sort-orderupdateMilestonesends{ id, input }againstmutation UpdateProjectMilestone($id: String!, $name: String, $description: String, $targetDate: TimelessDate, $sortOrder: Float).$idbinds (it's top-level), but$name/$description/$targetDate/$sortOrderare nested underinputand never bind — so the update succeeds with no changes and no error. This is worse than Bug 1 because it's silent.Root cause
src/services/milestone-service.ts(compileddist/services/milestone-service.js):Fix
export async function createMilestone(client, input) { - const result = await client.request(CreateProjectMilestoneDocument, { input }); + const result = await client.request(CreateProjectMilestoneDocument, input);export async function updateMilestone(client, id, input) { - const result = await client.request(UpdateProjectMilestoneDocument, { id, input }); + const result = await client.request(UpdateProjectMilestoneDocument, { id, ...input });Verified against the live Linear API: both
createandupdate(with--descriptionand--target-date) work after this change.Suggested follow-up
A small integration test that asserts the variables object shape matches each mutation document's declared variables would catch this class of
{ input }vs flat-variable mismatch across all services.