Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,22 @@ update a specific issue:
linear issue update TEAM-123
```

change labels:

```bash
# Add a label, keeping existing labels
linear issue update TEAM-123 --add-label bug

# Remove a label from this issue (does not delete it from the team)
linear issue update TEAM-123 --remove-label sprint-42

# Swap labels atomically in one update
linear issue update TEAM-123 --remove-label sprint-42 --add-label sprint-43

# Replace the entire label set
linear issue update TEAM-123 --label bug --label frontend
```

#### other issue commands

get issue id from current git branch:
Expand Down
5 changes: 4 additions & 1 deletion skills/linear-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ Write multi-line markdown to a file and pass `--description-file` (see the markd
```bash
linear issue update ENG-123 --state "In Review" --assignee sam
linear issue update ENG-123 --unassign
linear issue update ENG-123 --label infra --label security # replaces the label set
linear issue update ENG-123 --add-label security # add, keep existing labels
linear issue update ENG-123 --remove-label sprint-42 # detach; does not delete the label
linear issue update ENG-123 --remove-label sprint-42 --add-label sprint-43 # atomic swap
linear issue update ENG-123 --label infra --label security # replaces the label set
```

### Add a comment
Expand Down
5 changes: 4 additions & 1 deletion skills/linear-cli/SKILL.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ Write multi-line markdown to a file and pass `--description-file` (see the markd
```bash
linear issue update ENG-123 --state "In Review" --assignee sam
linear issue update ENG-123 --unassign
linear issue update ENG-123 --label infra --label security # replaces the label set
linear issue update ENG-123 --add-label security # add, keep existing labels
linear issue update ENG-123 --remove-label sprint-42 # detach; does not delete the label
linear issue update ENG-123 --remove-label sprint-42 --add-label sprint-43 # atomic swap
linear issue update ENG-123 --label infra --label security # replaces the label set
```

### Add a comment
Expand Down
6 changes: 5 additions & 1 deletion skills/linear-cli/references/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,11 @@ Options:
--estimate <estimate> - Points estimate of the issue
-d, --description <description> - Description of the issue
--description-file <path> - Read description from a file (preferred for markdown content)
-l, --label <label> - Issue label associated with the issue. May be repeated.
-l, --label <label> - Issue label associated with the issue; replaces the issue's entire label set.
May be repeated. Use --add-label/--remove-label to change labels incrementally.
--add-label <label> - Add a label to the issue, keeping its existing labels. May be repeated.
--remove-label <label> - Remove a label from the issue, keeping its other labels (does not delete the
label from the team). May be repeated.
--team <team> - Team associated with the issue (if not your default team)
--project <project> - Project to assign the issue to (UUID, slug ID, or name)
-s, --state <state> - Workflow state for the issue (by name or type)
Expand Down
14 changes: 10 additions & 4 deletions skills/linear-cli/references/organization-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,18 @@ echo -e "DEV-123\nDEV-124" | linear issue delete --bulk-stdin
linear initiative archive --bulk <id1> <id2>
```

## Adding Labels to Issues
## Updating Labels on Issues

```bash
# Add label to issue
linear issue update DEV-123 --label "Bug"
# Add a label, keeping the issue's existing labels
linear issue update DEV-123 --add-label "Bug"

# Add multiple labels
# Remove a label from this issue only (label delete removes it team-wide)
linear issue update DEV-123 --remove-label "sprint-42"

# Swap labels atomically in one update
linear issue update DEV-123 --remove-label "sprint-42" --add-label "sprint-43"

# Replace the entire label set with exactly these labels
linear issue update DEV-123 --label "Bug" --label "High Priority"
```
82 changes: 74 additions & 8 deletions src/commands/issue/issue-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,17 @@ export const updateCommand = new Command()
)
.option(
"-l, --label <label:string>",
"Issue label associated with the issue. May be repeated.",
"Issue label associated with the issue; replaces the issue's entire label set. May be repeated. Use --add-label/--remove-label to change labels incrementally.",
{ collect: true },
)
.option(
"--add-label <label:string>",
"Add a label to the issue, keeping its existing labels. May be repeated.",
{ collect: true },
)
.option(
"--remove-label <label:string>",
"Remove a label from the issue, keeping its other labels (does not delete the label from the team). May be repeated.",
{ collect: true },
)
.option(
Expand Down Expand Up @@ -104,6 +114,8 @@ export const updateCommand = new Command()
description,
descriptionFile,
label: labels,
addLabel,
removeLabel,
team,
project,
state,
Expand Down Expand Up @@ -134,6 +146,29 @@ export const updateCommand = new Command()
)
}

if (labels != null && (addLabel != null || removeLabel != null)) {
throw new ValidationError(
"Cannot combine --label with --add-label or --remove-label",
{
suggestion:
"--label replaces the issue's entire label set. Use it alone to set the exact set, or use --add-label/--remove-label alone to change it incrementally.",
},
)
}

// Label names resolve against the issue's (destination) team, so a
// team move combined with incremental label changes would silently
// make source-team labels unresolvable.
if (team != null && (addLabel != null || removeLabel != null)) {
throw new ValidationError(
"Cannot combine --team with --add-label or --remove-label",
{
suggestion:
"Move the issue with --team first, then change labels in a second update.",
},
)
}

// Validate that description and descriptionFile are not both provided
if (description && descriptionFile) {
throw new ValidationError(
Expand Down Expand Up @@ -211,15 +246,41 @@ export const updateCommand = new Command()
}
}

const labelIds: string[] = []
if (labels != null && labels.length > 0) {
for (const label of labels) {
const labelId = await getIssueLabelIdByNameForTeam(label, teamKey)
// Resolves label names to IDs, deduped by resolved ID so case
// variants of the same label collapse to one entry.
const resolveLabelIds = async (names: string[]): Promise<string[]> => {
const ids: string[] = []
for (const name of names) {
const labelId = await getIssueLabelIdByNameForTeam(name, teamKey)
if (!labelId) {
throw new NotFoundError("Issue label", label)
throw new NotFoundError("Issue label", name, {
suggestion:
`Run \`linear label list --team ${teamKey}\` to see available labels.`,
})
}
if (!ids.includes(labelId)) {
ids.push(labelId)
}
labelIds.push(labelId)
}
return ids
}

const labelIds = labels != null ? await resolveLabelIds(labels) : []
const addedLabelIds = addLabel != null
? await resolveLabelIds(addLabel)
: []
const removedLabelIds = removeLabel != null
? await resolveLabelIds(removeLabel)
: []

if (addedLabelIds.some((id) => removedLabelIds.includes(id))) {
throw new ValidationError(
"Cannot add and remove the same label in one update",
{
suggestion:
"Remove the duplicate label from either --add-label or --remove-label.",
},
)
}

let projectId: string | undefined = undefined
Expand Down Expand Up @@ -289,7 +350,12 @@ export const updateCommand = new Command()
if (priority !== undefined) input.priority = priority
if (estimate !== undefined) input.estimate = estimate
if (finalDescription !== undefined) input.description = finalDescription
if (labelIds.length > 0) input.labelIds = labelIds
if (labels != null) {
input.labelIds = labelIds
} else {
if (addLabel != null) input.addedLabelIds = addedLabelIds
if (removeLabel != null) input.removedLabelIds = removedLabelIds
}
if (teamId !== undefined) input.teamId = teamId
if (projectId !== undefined) input.projectId = projectId
if (projectMilestoneId !== undefined) {
Expand Down
124 changes: 123 additions & 1 deletion test/commands/issue/__snapshots__/issue-update.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ Options:
--estimate <estimate> - Points estimate of the issue
-d, --description <description> - Description of the issue
--description-file <path> - Read description from a file (preferred for markdown content)
-l, --label <label> - Issue label associated with the issue. May be repeated.
-l, --label <label> - Issue label associated with the issue; replaces the issue's entire label set.
May be repeated. Use --add-label/--remove-label to change labels incrementally.
--add-label <label> - Add a label to the issue, keeping its existing labels. May be repeated.
--remove-label <label> - Remove a label from the issue, keeping its other labels (does not delete the
label from the team). May be repeated.
--team <team> - Team associated with the issue (if not your default team)
--project <project> - Project to assign the issue to (UUID, slug ID, or name)
-s, --state <state> - Workflow state for the issue (by name or type)
Expand Down Expand Up @@ -219,3 +223,121 @@ https://linear.app/test-team/issue/ENG-123/some-issue
stderr:
""
`;

snapshot[`Issue Update Command - Add Label Sends addedLabelIds 1`] = `
stdout:
"Updating issue ENG-123

✓ Updated issue ENG-123: Some issue
https://linear.app/test-team/issue/ENG-123/some-issue
"
stderr:
""
`;

snapshot[`Issue Update Command - Add Label Dedupes Case Variants 1`] = `
stdout:
"Updating issue ENG-123

✓ Updated issue ENG-123: Some issue
https://linear.app/test-team/issue/ENG-123/some-issue
"
stderr:
""
`;

snapshot[`Issue Update Command - Remove Label Sends removedLabelIds 1`] = `
stdout:
"Updating issue ENG-123

✓ Updated issue ENG-123: Some issue
https://linear.app/test-team/issue/ENG-123/some-issue
"
stderr:
""
`;

snapshot[`Issue Update Command - Add And Remove Label In One Update 1`] = `
stdout:
"Updating issue ENG-123

✓ Updated issue ENG-123: Some issue
https://linear.app/test-team/issue/ENG-123/some-issue
"
stderr:
""
`;

snapshot[`Issue Update Command - Label Replace Sends Exact labelIds 1`] = `
stdout:
"Updating issue ENG-123

✓ Updated issue ENG-123: Some issue
https://linear.app/test-team/issue/ENG-123/some-issue
"
stderr:
""
`;

snapshot[`Issue Update Command - Label And Add Label Conflict 1`] = `
stdout:
""
stderr:
"✗ Failed to update issue: Cannot combine --label with --add-label or --remove-label
--label replaces the issue's entire label set. Use it alone to set the exact set, or use --add-label/--remove-label alone to change it incrementally.
"
`;

snapshot[`Issue Update Command - Label And Remove Label Conflict 1`] = `
stdout:
""
stderr:
"✗ Failed to update issue: Cannot combine --label with --add-label or --remove-label
--label replaces the issue's entire label set. Use it alone to set the exact set, or use --add-label/--remove-label alone to change it incrementally.
"
`;

snapshot[`Issue Update Command - Team Move And Add Label Conflict 1`] = `
stdout:
""
stderr:
"✗ Failed to update issue: Cannot combine --team with --add-label or --remove-label
Move the issue with --team first, then change labels in a second update.
"
`;

snapshot[`Issue Update Command - Team Move And Remove Label Conflict 1`] = `
stdout:
""
stderr:
"✗ Failed to update issue: Cannot combine --team with --add-label or --remove-label
Move the issue with --team first, then change labels in a second update.
"
`;

snapshot[`Issue Update Command - Add And Remove Same Label Conflict 1`] = `
stdout:
""
stderr:
"✗ Failed to update issue: Cannot add and remove the same label in one update
Remove the duplicate label from either --add-label or --remove-label.
"
`;

snapshot[`Issue Update Command - Unknown Add Label 1`] = `
stdout:
""
stderr:
"✗ Failed to update issue: Issue label not found: nosuch
Run \`linear label list --team ENG\` to see available labels.
"
`;

snapshot[`Issue Update Command - Unknown Remove Label 1`] = `
stdout:
""
stderr:
"✗ Failed to update issue: Issue label not found: nosuch
Run \`linear label list --team ENG\` to see available labels.
"
`;
Loading
Loading