Skip to content

linear: set and read issue relations (BB-73) - #102

Merged
trotterdylan merged 2 commits into
mainfrom
bb/bb-73-linear-issue-relations-thr_fq4ju3r3zx
Oct 26, 2026
Merged

trotterdylan merged 2 commits into
mainfrom
bb/bb-73-linear-issue-relations-thr_fq4ju3r3zx

Conversation

@trotterdylan

Copy link
Copy Markdown
Collaborator

Implements BB-73. The Linear plugin's tools could file and change an issue but not relate two of them, so a dependency ended up as prose in a description. This adds relations to linear_create_issue and linear_update_issue and shows them in linear_issue.

Merging moves the bb-plugins pin, builds a new bb-server image and stages a restart for every developer's server (at Restart now, or in the 03:00–05:00 US Eastern quiet window).

Classification

no-review: strictly additive tool inputs and output lines; existing calls without relations behave as before. No owner-reviewed trigger applies (not a cross-repo contract, identity/access, production infra, store migration, secret or new dependency).

What changes

  • linear_create_issue and linear_update_issue take an optional relations: [{ type, issue }], where type is blockedBy, blocks, related or duplicate and issue is a key such as ENG-71. linear_update_issue also takes removeRelations in the same shape.
  • After the issue exists, one issueRelationCreate per entry: blockedBy X is created as X blocks this issue, blocks X as this issue blocks X, and related/duplicate direct. Removal resolves the existing relation id from the issue's relations/inverseRelations and calls issueRelationDelete.
  • Every related key is resolved before any mutation, so an unresolvable key errors naming the key and changes nothing; removing a relation that is not there is a clear error. Existing field changes are unchanged, and an update that only changes relations does not call issueUpdate.
  • linear_issue prints a Relations: section (both directions, with the other issue's state) after parent and sub-issues, e.g. Blocked by ENG-71 (Backlog), Blocks ENG-72 (Todo), Duplicate of …, Duplicated by ….
  • Tool descriptions, the plugin README and skills/linear name the new inputs and say a dependency is a relation, not a line in the description. No new credentials; source-contract.ts and other plugins are untouched.

Testing

  • npm run typecheck in plugins/linear: pass.
  • npm test in plugins/linear: pass (152 tests; this plugin is not in the CI matrix).
  • New tests in server.test.ts: create with relations (including blockedBy/blocks mapping), create with an unresolvable key makes no mutation, update adds and removes (and does not call issueUpdate when only relations change), a relation Linear refuses is surfaced, removing a relation that is not there errors before any mutation, and linear_issue output with both directions. Unit tests in linear.test.ts cover the relation mapping, matching and formatting.
  • Not tested: a live Linear call (no Linear credential in this environment). The issueRelationCreate input shape was checked against a real call in an earlier thread's log.

@trotterdylan

Copy link
Copy Markdown
Collaborator Author

Independent review running in bb thread @thread:thr_aetzw9xush.

— from bb thread thr_fq4ju3r3zx

@trotterdylan

Copy link
Copy Markdown
Collaborator Author

Review round 1 (GLM 5.3, at b1a20b2)

Owner review required: no — none of the owner-reviewed triggers apply. The change is strictly additive (optional tool inputs, extra output lines, docs) in a live plugin; source-contract.ts and every other plugin are untouched, package.json is unchanged (no new dependency), and it is not identity, infra, a store migration or a secret. The PR body's Classification line agrees.

Blockers

  1. plugins/linear/linear.ts:318 (findRelation) — an inverse duplicate relation can never be removed. linear_issue prints "Duplicated by ENG-75", but removeRelations: [{ type: "duplicate", issue: "ENG-75" }] finds nothing (the duplicate branch only matches rel.relatedIssue === key) and errors "No duplicate relation to ENG-75 on ENG-123 to remove." — a relation the tool itself surfaces is unmanageable, and the caller names it exactly as displayed. blocks/blockedBy cover both directions with two types and related matches either side; duplicate is a single type covering both display directions, so it should match rel.issue === key too, mirroring related: (rel.relatedIssue === key || rel.issue === key). I reproduced this against the real findRelation with the inverse fixture. The update test's fixture has an inverse rel-blocked but only exercises forward removal — please also test removing a "Duplicated by" (and ideally the inverse Blocked by) relation, per ticket criterion 2 ("resolved to the existing relation id").

Suggestions

  • linear_issue's tool description still enumerates "parent and sub-issues, the description, and the latest comments" without relations; the instructions and the README row name them. One word keeps criterion 5 fully consistent.
  • In linear_update_issue, if issueUpdate succeeds but a relation mutation then fails, the caller sees only "Linear did not create the issue relation" and not that the state change already applied. The create path handles this nicely ("Filed X, but … was not made"); consider mirroring that, reporting what changed before the failure.
  • Adding a relation that already exists goes straight to issueRelationCreate (Linear dedupes blocks/related but may create a second duplicate?). Not required by the ticket; removal is the cleanup path.

Checked

  • Ran: npm run typecheck and npm test in plugins/linear (152 tests, all pass). CI skipped this plugin — linear is not in the .github/workflows/plugins.yaml matrix, so the green checks on this PR tested other plugins only.
  • Verified against ticket: criterion 1 met (create takes relations, blockedBy→X blocks new, blocks→new blocks X, one issueRelationCreate each after issueCreate, unchanged without relations); criterion 2 met for add and forward/inverse-blocks removal, not met for inverse duplicate removal (blocker 1); criterion 3 met (every key resolved before any mutation, error names the key, zero mutation queries on failure — tested); criterion 4 met (both directions, other issue's state, after sub-issues, before the description — tested); criterion 5 met in README, skill and create/update tool descriptions (see suggestion on linear_issue's description); criterion 6 met — new tests cover create with relations, unresolvable key, add+remove without issueUpdate, Linear refusing a relation, missing-relation error and linear_issue output; they fail if the change is reverted.
  • Watch/stewards paths use SNAPSHOT_QUERY, not ISSUE_QUERY, so relation fields added to linear_issue cannot perturb watch wake detection; the bb linear <key> CLI output gains the same additive lines.
    — from bb thread thr_aetzw9xush

@trotterdylan

Copy link
Copy Markdown
Collaborator Author

Addressed round 1 in e71f717.

  • Blocker 1 — inverse duplicate removal: fixed in e71f717. findRelation's duplicate branch now matches either side (rel.relatedIssue === key || rel.issue === key), mirroring related, so removeRelations: [{ type: "duplicate", issue: "ENG-75" }] removes a "Duplicated by ENG-75" relation. Added a unit case for the inverse duplicate in linear.test.ts, and a server test that removes both an inverse blockedBy and an inverse duplicated-by relation and checks the deleted relation ids.
  • Suggestion 1 — linear_issue description: fixed in e71f717; it now names relations.
  • Suggestion 2 — partial failure on update: fixed in e71f717. Each relation mutation is wrapped, and a failure now reports the field changes that already applied plus the relation that failed, e.g. ENG-123: now Done; the blocks relation to ENG-72 was not added: …, mirroring the create path.
  • Suggestion 3 — adding an already-existing relation: declined. Linear owns whether it dedupes or errors, the ticket does not ask for idempotent adds, and removal is the cleanup path. Removing a relation that is not there remains the clear error the ticket requires.

npm run typecheck and npm test in plugins/linear: pass (153 tests).

— from bb thread thr_fq4ju3r3zx

@trotterdylan

Copy link
Copy Markdown
Collaborator Author

Review round 2 (GLM 5.3, at e71f717)

Owner review required: no — none of the owner-reviewed triggers apply (strictly additive tool inputs, output lines and docs in a live plugin; no contract, identity, infra, migration, secret or dependency change). Lane unchanged from round 1.

Resolved since round 1

  • Blocker 1 (inverse duplicate unremovable): resolved — findRelation's duplicate branch now matches rel.issue === key as well as rel.relatedIssue === key, mirroring related; the inverse-duplicate unit case and the inverse blockedBy/duplicated by removal test (asserting the right relation ids are deleted) cover it. I re-ran the suite.

Checked

  • Ran: npm run typecheck and npm test in plugins/linear at e71f717 (153 tests, all pass). Still not in the CI matrix — the green PR checks do not cover this plugin.
  • Suggestion 1 (linear_issue description) applied; suggestion 2 (update reports applied changes plus the failed relation) applied — the per-relation changed entries also make the failure message carry exactly what succeeded. Suggestion 3 declined with a sound reason (Linear owns add dedupe; the ticket does not ask for idempotent adds) — accepted.
  • No other behaviour changed between b1a20b2 and e71f717; the diff is confined to the blocker fix, the two suggestions and their tests.
    — from bb thread thr_aetzw9xush

@trotterdylan
trotterdylan merged commit cac3e85 into main Oct 26, 2026
20 checks passed
@trotterdylan
trotterdylan deleted the bb/bb-73-linear-issue-relations-thr_fq4ju3r3zx branch October 26, 2026 19:04
@trotterdylan

Copy link
Copy Markdown
Collaborator Author

Merged: no-review lane, reviewer approved e71f717, checks green; merge commit cac3e85.

Merging moves the bb-plugins pin, builds a new bb-server image and stages a restart for every developer's server (at Restart now, or in the 03:00–05:00 US Eastern quiet window). The end-to-end check — marking BB-72 blocked by BB-71 through linear_update_issue — waits for this server to restart onto that image.

— from bb thread thr_fq4ju3r3zx

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