linear-inbox-cleanup
Mark as read every unread Linear inbox notification that needs no action, archive every read one that needs no action, and leave the rest unchanged. Use when the user asks to clean up their Linear inbox, or on a schedule. Changes read and archive state, so answer read-only questions about the inbox directly instead.
- License 许可
- MIT
- 最近更新
- 2026-11-11
- Stars
- ★ 1
- Forks
- 1
01 前言 Front Matter
- name
- linear-inbox-cleanup
- description
- Mark as read every unread Linear inbox notification that needs no action, archive every read one that needs no action, and leave the rest unchanged. Use when the user asks to clean up their Linear inbox, or on a schedule. Changes read and archive state, so answer read-only questions about the inbox directly instead.
03 SKILL.md 正文
Linear Inbox Cleanup
A notification needs no action when it is done, status noise, triage-assigned, superseded, or idle. Clear every unread notification that needs no action. Archive every read notification that needs no action, whatever its age. Keep every other notification. When in doubt about a notification, keep it.
A notification is read when its readAt is non-null. When the response omits
readAt, treat the notification as unread.
Clear means mark_notification with id set to the notification ID and
read: true. Archive means the GraphQL notificationArchive mutation, sent as
in Archive. Keep means leave the notification unchanged: an unread
one stays unread, and a read one stays read and in the inbox.
In Categories and Superseded, "clear" means the notification needs no action. Run step 8 decides whether it is cleared or archived.
Scope
Use only these Linear MCP tools: get_user, get_notifications, get_issue,
get_diff, list_comments, and mark_notification. Do not use a browser.
The only non-MCP actions are two shell commands: the key check in
Run step 3, which reaches no service, and the curl call in
Archive. The curl call sends notificationArchive, the only
GraphQL operation this skill may send. Send no other GraphQL query or
mutation.
The only writes are clear, through mark_notification, and archive, through
notificationArchive. Never snooze, delete, or unarchive a notification, and
never comment on or change an issue, project, document, comment, or pull
request.
Categories
- Done. The notification's issue has status type
completedorcanceled, or its pull request has statusmerged. A Duplicate status is typecanceled. Any other issue status type, and an open pull request or one closed without merging, is not done. Nothing is left to act on, so clear a done notification whatever its type, mentions included. - Status noise.
categoryisstatusChanges. That coversissueStatusChanged,issueRestored,issueUnblocked, and every other status-change type. Clear it whatever the issue's status. - Triage-assigned.
typeisissueAssignedToYouandsubtitleisAssigned by Triage Rule. An assignment by a person or a bot is not triage-assigned. - Superseded. A later event shows the user already acted, or the work moved on. See Superseded.
- Idle. A new-comment notification where the user is not @-mentioned and has not commented in that thread.
Issue URLs below use <KEY>-<N>, the issue identifier with its team key, such
as ENG-123.
Notifying comment
Find a notification's notifying comment and thread this way:
- Find the parent from the
url./issue/<KEY>-<N>/...givesissueId: "<KEY>-<N>",/document/<slug>givesdocumentId, and/project/<slug>givesprojectId. The comment ID prefix is the eight hex characters after#comment-. - Call
list_commentsfor the parent withlimit: 250, followingcursor. - The notifying comment is the one whose
idstarts with the prefix. - The thread root is the comment's
parentIdwhen set, or else the comment's ownid. The thread is the root plus every comment whoseparentIdis the root.
Superseded
Check each condition in this order, and record the first that matches as the
reason. A condition matches only the notification types listed for it. A
condition that does not apply to a notification is a silent non-match. When a
condition applies but a lookup it needs fails, or a field it needs is missing,
keep the notification and, when it is unread, list it under Unresolved. No
later check runs on it, Idle included. A field is missing when the response
omits it. A field the response returns as null is present and empty.
Compare timestamps as instants and dates in UTC.
- Answered. Types:
issueNewComment,issueCommentMention,documentNewComment,documentCommentMention,projectNewComment, andprojectCommentMention, with aurlthat has#comment-<prefix>. A comment in the thread hasauthor.idequal to the user'sidandcreatedAtlater than the notifying comment'screatedAt. A comment with onlyonBehalfOf.idequal to the user'siddoes not count. - Reviewed. Types:
pullRequestReviewRequestedandpullRequestReviewRerequested, pluspullRequestCommented,pullRequestCommentMention, andpullRequestMentionwhensubtitlecontains "ready for review", case-insensitive. Theurlis a/review/URL. ReadviewerReviewStatefromget_diff. A nulllastSubmittedAtorlastSubmittedDecisionmatches neither bullet. Clear when either holds:lastSubmittedAtis later than the notification'supdatedAtandcreatedAt, andisAskedToReviewAgainis false. Any decision counts, changes requested included.- For the two request types only:
lastSubmittedDecisionisapproved, andisReviewRequestedandisAskedToReviewAgainare both false.
- Advanced. Type:
issueNewComment. The notifying comment's body begins with> *This was generated by AI during triage.*and does not @-mention the user. The issue'sstatusTypeisunstartedorstarted. - Reassigned. Type:
issueAssignedToYou. The issue'sassigneeIdis not the user'sid. An unassigned issue counts. - Rescheduled. Type:
issueDue. The notice is overdue when itssubtitlecontains "overdue", case-insensitive, and due soon otherwise. Clear either kind when the issue'sdueDateis empty. Also clear an overdue notice whendueDateis later than the date of the notification'screatedAtand is today or later. - Restated. Types: the Answered types. Check this condition last, after
the others have run over every notification. Clear this notification when
another one with the same
typeand a latercreatedAtis still pending, and its notifying comment is in the same thread as this one's, with the sameauthor.id. For an unread notification, the later one is pending when it is still unread after every earlier clear in this run. For a read notification, the later one is pending when it is unread, or when it is read and no earlier check matched it. When either comment or its author cannot be resolved, this condition does not match.
Run
Keep one cache for the whole run, keyed by issue identifier, review URL, and comment parent. Every step reads from it, so each is fetched at most once.
-
Identify the user. Call
get_userwithquery: "me". Keep the user'sidandname.get_userreturns no profile URL, so build it ashttps://linear.app/<workspace>/profiles/<displayName>, taking<workspace>from any notificationurlin step 2. A comment body @-mentions the user only when it contains that profile URL or a mention link to it. The user's name or@<displayName>as plain text, without the profile link, is not a mention, because Linear does not notify it as one. -
Fetch every notification. Call
get_notificationswithlimit: 250and withoutunreadOnly. FollowcursoruntilhasNextPageis false. Split the result into unread and read notifications. -
Set read notifications aside. Check whether the key is set with
[ -n "$LINEAR_API_KEY" ] && echo set || echo unset, which never prints the key. When it is unset, keep every read notification without lookups and skip archiving for the run. Otherwise, keep without lookups each read notification whosesnoozedUntilAtis later than now. Steps 4 to 8 cover the unread notifications and the remaining read ones. -
Clear status noise and triage-assigned. Neither needs a lookup.
-
Clear the done ones among the rest. Look up each notification's status from its
url:/issue/<KEY>-<N>/...: callget_issuefor<KEY>-<N>withfieldsstatusType,assigneeId, anddueDate, and read its status type./review/<slug>: callget_diffwith the review URL, without its#comment-fragment, and read its status.
A notification whose lookup fails, or shows no status type, is not done.
-
Clear the superseded ones among the rest, by Superseded.
-
Find the idle candidates among the notifications not yet cleared or kept as unresolved. A candidate reports a new comment or reply:
categoryiscommentsAndReplies, ortypeis a new-comment or reply type, such asissueNewComment,projectUpdateNewComment, or a document or project comment type. Keep every other notification without further checks, including:- any
typecontainingMention, such asissueMentionorissueCommentMention, orcategorymentions - reactions, meaning any
typecontainingReaction - review requests, re-requests, approvals, and all other pull-request or
reviewsactivity - assignments not made by the Triage Rule, document content changes, project updates, and anything else that is not a comment
Then check each candidate:
- Find the notifying comment and thread by Notifying comment. Keep the notification when the parent, the prefix, or a matching comment cannot be found.
- Keep the notification when any comment in the thread has
author.idoronBehalfOf.idequal to the user'sid. - Keep the notification when the notifying comment's body @-mentions the user.
- Otherwise, clear it.
- any
-
Write. Steps 4 to 7 only decide. Write after every decision is made. Clear each unread notification that needs no action with
mark_notification. Archive each read notification that needs no action, one at a time, by Archive. A notification cleared in this run is never archived in this run. A later run archives it once it is read.
Archive
Archive one notification with this shell command, replacing <id> with the
notification id. It reads the key from the environment and prints the
response, which holds only success or errors, and the HTTP status. It never
prints the key.
case "$LINEAR_API_KEY" in lin_api_*) auth="$LINEAR_API_KEY" ;; *) auth="Bearer $LINEAR_API_KEY" ;; esac
curl -sS https://api.linear.app/graphql \
-H "Authorization: $auth" -H 'Content-Type: application/json' \
-w '\nHTTP %{http_code}\n' \
-d '{"query":"mutation($id: String!) { notificationArchive(id: $id) { success } }","variables":{"id":"<id>"}}'
An archive fails on a curl error, an HTTP status other than 200, an errors
entry in the response, or success other than true. On the first failure,
send no further notificationArchive in this run. Count the failed
notification and every one not yet sent as kept read. A wrong key, a key
for another user, and a rate limit all fail this way.
Report
End with a short summary.
- Counts. Unread scanned, read scanned, cleared per reason, archived per reason, idle candidates, kept unread, and kept read. Unread scanned equals cleared plus kept unread. Read scanned equals archived plus kept read. So scanned equals cleared plus archived plus kept.
- Archiving skipped. When
LINEAR_API_KEYis unset, one line saying archiving was skipped becauseLINEAR_API_KEYis not set. - Cleared. One line per cleared notification, in this form:
[<KEY>-<N>](issue url) — <title> — <author or notification type> (reason: <reason>). The reason is one ofissue completed,issue canceled,PR merged,status change,assigned by Triage Rule,superseded: answered,superseded: reviewed,superseded: advanced,superseded: reassigned,superseded: rescheduled,superseded: restated, ornot mentioned, not in thread. For a pull request, use the pull request's title as the link text and link its Linear review page. For a document or project, use its title as the link text and link its Linear page. Never invent an issue identifier. - Archived. One line per archived notification, in the Cleared form and
reason vocabulary, followed by the notification
id. - Unresolved. Every unread notification kept because something could not be resolved, with the reason: a failed lookup, no status type, a missing field a Superseded condition needs, no parent or comment prefix, or no matching comment. After an archive failure, add one line with the error message and the count of the failed notification plus those not yet sent. A read notification that cannot be resolved is counted as kept read and is not listed here.
Render every Linear issue identifier as a Markdown link to its Linear URL.
04 同仓库更多 Skills
writing-for-pstack
Write or tighten an operator prompt for a patina-mode run. Use when the operator asks to write, revise, or review a prompt that will be passed to patina-mode.
writing-for-agents
Writing documents for agents. Use when creating or editing skills, or modifying AGENTS.md or CLAUDE.md.
wizard
Generate an interactive bash wizard that walks a human through steps only they can perform. Use when provisioning infrastructure, setting up credentials or CI secrets, walking an unfamiliar third-party dashboard, or running a one-off migration or cutover. Don't invoke this for steps the agent can perform itself.
wayfinder
Plan a huge chunk of work (more than one agent session can hold) as a shared map of decision tickets on your issue tracker, and resolve them one at a time until the way to the destination is clear.
wait-what
Stop. That last message did not land: re-pitch it.
using-github
Work with repository settings, pull requests, reviews, checks, and releases on GitHub. Use for GitHub operations other than issue tracking.