Skip to content

milestones create/update: variables wrapped in { input } but mutations use flat variables (create hard-fails; update silently drops fields) #228

Description

@ryanrozich

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions