Assign Linear Issues to the Current On-Call with Pagerly
Linear has a first-class answer to triage ownership. Here is how to drive it from your real on-call rota, and what to do for issues that never reach triage.
Linear is unusual among trackers in that it has a native concept for the problem this post is about. Triage Responsibility is Linear's answer to "who picks up the things nobody assigned", and unlike a Jira component or a Slack usergroup it resolves to an actual person.
What it does not have is a rota. It has a schedule primitive, but something has to fill it in and keep it correct through overrides, swaps and holidays. That is the part Pagerly does.
Triage Responsibility: the native path
Triage is Linear's inbox for issues that arrive without an owner — customer bug reports, error tracker output, form submissions, anything filed by someone outside the team. Triage Responsibility decides who picks them up.
Pagerly keeps a Linear time schedule in sync with your Pagerly rotation, then binds that schedule to the team's Triage Responsibility with the assign action. The result is that Linear itself does the assignment: an issue lands in triage and is on the current on-call engineer's plate before anyone opens the app.
The reason to prefer this over calling the API yourself is not effort, it is durability. Pagerly rewrites the schedule when the rota changes, so an override taken at 2pm is reflected in Linear immediately, and the triage rule itself never has to be touched. An automation that hard-codes assignees has to be maintained; a schedule that is kept in sync does not.
Setting it up
Three steps: connect your Linear account to Pagerly, connect the Pagerly team to the Linear team, then enable triage assignment. Pagerly creates the Linear time schedule and the triage responsibility for you, pushing schedule entries as start/end windows with a resolved Linear user for each.
The resolution is by email, which is the one thing to check before you start. If someone's Pagerly email does not match their Linear account email, they will be silently missing from the generated schedule.
This works best on top of a real Pagerly schedule rotation rather than an ad-hoc list, because the Linear schedule mirrors your shift boundaries directly.
The limit worth knowing
Triage Responsibility only fires for issues that actually enter triage. An issue created directly in a project by a team member — the majority of issues in most Linear workspaces — bypasses triage entirely and stays unassigned unless the creator assigns it.
That is usually fine, because an engineer filing an issue in their own project generally knows who should own it. But if you have services filing issues via the API, or a bug intake path that writes straight into a project, those need the second approach.
The GraphQL path
Two steps: resolve the on-call user's email to a Linear user ID, then update the issue.
Who is on call
curl -s 'https://api.pagerly.io/pagerly/o/currentusers?teamname=devops' \
-H "X-APIKEY: $PAGERLY_API_KEY"
[{ "name": "mansi", "email": "mansi@pagerly.io", "id": "U04CTTV5Z6G", "imageurl": null }]
Email to Linear user ID
query {
users(filter: { email: { eq: "mansi@pagerly.io" } }) {
nodes {
id
email
name
}
}
}
If nodes comes back empty, the on-call user has no Linear account with that email. Treat that as a hard failure and alert on it — a swallowed empty result here is exactly how you end up with an automation that has quietly done nothing for three weeks.
Assign the issue
mutation {
issueUpdate(
id: "<issue-uuid>"
input: { assigneeId: "<linear-user-id>" }
) {
success
issue {
id
identifier
title
url
assignee { id name }
}
}
}
Check the success field, not just the HTTP status. Linear returns 200 with success: false for a rejected mutation, so a status-code check alone will report a failure as a success.
Note also that id here is the issue UUID, not the human identifier. Passing ENG-42 where a UUID is expected is a common first-attempt error, and the failure mode is a success: false rather than anything more informative.
Better: assign at creation
If your service files the issue itself, set the assignee in the same call rather than creating and then updating. One round trip, and no window in which the issue exists unassigned:
mutation {
issueCreate(
input: {
teamId: "<linear-team-id>"
title: "Checkout returns 500 for EU cards"
description: "..."
assigneeId: "<linear-user-id>"
}
) {
success
issue { id identifier url assignee { id name email } }
}
}
End to end
#!/usr/bin/env bash
set -euo pipefail
TEAM="devops"
ISSUE_ID="$1"
EMAIL=$(curl -sf "https://api.pagerly.io/pagerly/o/currentusers?teamname=$TEAM" \
-H "X-APIKEY: $PAGERLY_API_KEY" | jq -r '.[0].email // empty')
if [ -z "$EMAIL" ]; then
echo "Nobody on call for $TEAM — leaving issue unassigned" >&2
exit 1
fi
USER_ID=$(curl -sf https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H 'Content-Type: application/json' \
-d "{\"query\":\"query { users(filter: {email: {eq: \\\"$EMAIL\\\"}}) { nodes { id } } }\"}" \
| jq -r '.data.users.nodes[0].id // empty')
if [ -z "$USER_ID" ]; then
echo "On-call user $EMAIL has no Linear account" >&2
exit 1
fi
curl -sf https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H 'Content-Type: application/json' \
-d "{\"query\":\"mutation { issueUpdate(id: \\\"$ISSUE_ID\\\", input: {assigneeId: \\\"$USER_ID\\\"}) { success } }\"}"
The two explicit failure branches are the point of the example. Both cases are real, both are silent if you do not check for them, and both produce the exact symptom you were trying to fix.
Should issues move at handover?
Triage Responsibility handles new issues. It does not move issues that are already open, which raises the question of whether they should move at all when the shift changes.
The honest answer is: only the untouched ones. An issue that is still sitting in the state it was created in has no context to lose, and moving it to the incoming on-call is strictly better than leaving it with someone who has gone home. An issue someone has been investigating for two hours is a different object — the comments, the branch, the half-formed theory all live with that person, and reassigning it on a schedule destroys more than it distributes.
A scheduled job that reassigns issues in the initial state, and leaves anything in progress alone, gets this about right. Anything more aggressive tends to get turned off within a month.
Troubleshooting
The user lookup returns nothing. Pagerly email does not match Linear email. Align the primary email in one place or the other.
issueUpdate returns success: false. Usually a human identifier passed where a UUID belongs.
Triage assignment stopped working. The Pagerly team is no longer linked to the Linear team — reconnect them and the schedule regenerates.
Issues created in projects are never assigned. Expected: they never entered triage. Use the GraphQL path, or route your intake through triage so the native rule applies.
Full details are in the Linear assignment documentation. For the same pattern in other tools, see assigning bugs and tickets to the current on-call.
