Cloudflare Worker that integrates Linear with Open-Inspect as a first-class
Linear Agent. Users can @mention or assign the agent on issues to trigger background coding
sessions.
For day-to-day usage, see the user-facing Linear integration guide.
@OpenInspect on issue → Linear sends AgentSessionEvent webhook →
Agent emits "Thinking..." → Resolves repo → Creates session →
Prompt reaches sandbox → Eligible issue may move to team's lowest-position started state →
Agent emits "Working on owner/repo..." → Agent codes in sandbox →
Completion callback → Agent emits "PR opened: <link>"
- A user
@mentionsor assigns the agent on a Linear issue - Linear sends an
AgentSessionEventwebhook to this worker - The worker emits a
Thoughtactivity (visible in Linear as "thinking") - Resolves the target GitHub repo (see Repo Resolution below)
- Creates an Open-Inspect coding session and sends the issue as a prompt
- After a human-initiated prompt reaches a live sandbox, moves an eligible issue to the team's
lowest-position
startedworkflow state when the signed callback is delivered and accepted - Emits a
Thoughtactivity with a link to the live session - When the agent completes, emits a final
Responsewith the PR link
Follow-up messages on an issue with an active session are sent as additional prompts to the existing session rather than creating a new one. Stopping or cancelling the agent in Linear kills the sandbox session.
The issue transition is opt-in and best-effort. Already-started, completed, canceled, automation-created, and follow-up sessions remain unchanged. A skipped, rejected, or failed transition never blocks agent execution.
Go to Linear Settings → API → Applications → New
Fill in:
- Application name:
OpenInspect(this is how the bot appears in @mentions) - Developer name: Your org name
- Callback URL:
https://<your-linear-bot-worker>/oauth/callback - Webhooks: Enable, set URL to
https://<your-linear-bot-worker>/webhook - Webhook events: Check Agent session events, Issues, Comments
- Client credentials tokens: Enable this option. The Worker uses these 30-day app-actor tokens for runtime API calls.
- Public: OFF (unless distributing to other workspaces)
Note the Client ID, Client Secret, and Webhook Signing Secret.
Set enable_linear_bot = true and add to your terraform.tfvars:
enable_linear_bot = true
linear_client_id = "your-client-id"
linear_client_secret = "your-client-secret"
linear_webhook_secret = "your-webhook-signing-secret"The worker also requires these secrets (set via wrangler secret put or Terraform):
- Exactly one classifier credential selected by
CLASSIFICATION_MODEL:ANTHROPIC_API_KEYfor an Anthropic model (the default), orOPENAI_API_KEYfor an OpenAI model SERVICE_AUTH_SECRET— per-service sig1 signing secret; also verifies CP callbacks
Then terraform apply.
Visit https://<your-linear-bot-worker>/oauth/authorize in your browser. This initiates the OAuth
flow with actor=app and installs the agent in your Linear workspace.
Requires admin permissions in the Linear workspace.
After installation, @OpenInspect will appear in the mention and assignee menus.
The browser authorization installs the app actor. The Worker then uses the application's client ID and client secret to mint runtime tokens; authorization-code access and refresh tokens are not kept as runtime credentials.
Before deploying a version that uses client credentials, open the existing application in Linear Settings → API → Applications and enable Client credentials tokens. Terraform cannot change this Linear-side setting.
For a private, single-workspace deployment whose application credentials resolve to the installed workspace, deploy normally after enabling the setting. No uninstall/reinstall, new secret, webhook change, or scope reauthorization is expected. The first Linear request mints and verifies a runtime token, then removes the legacy refresh-token record. Allow already-running sessions to finish before upgrading; callback contexts created by older versions may not contain the installed app-user identity required for terminal Agent API delivery.
If the setting is not enabled, Linear reports that the client does not support the
client_credentials grant and the request fails without falling back to the legacy refresh token.
If the OAuth application is managed in a different workspace from the installed agent, verify that
the client-credentials token's viewer organization matches the webhook organization before
upgrading; a mismatch is rejected. Rotating the Linear client secret invalidates cached runtime
tokens; deploy the replacement secret and the Worker will mint a replacement token on the next cache
miss or HTTP 401.
These are target-selection mappings, not Open-Inspect team ownership bindings. Bind the external Linear team ID in the Open-Inspect team's Channels tab to route new sessions to that team, and configure its members and repository grants separately. The teams database migration does not convert the legacy KV mappings below into bindings or reassign existing sessions. Both mapping types can remain in use for target selection after a binding is added.
Linear's unboundChannels policy defaults to workspace; reject requires a binding before a new
delegation can launch. Scoped repository, environment, and integration-setting reads fail closed
instead of falling back to workspace catalogs. See
Team Bindings and Existing Mappings
for scope, actorless reads, callback checks, and the default shadow enforcement caveat.
The agent resolves repos automatically in most cases (see Repo Resolution).
Static mappings are optional overrides, stored in the worker's KV namespace and edited directly with
wrangler (the key shapes are documented in src/kv-store.ts):
Team → target mapping:
npx wrangler kv key put --namespace-id <LINEAR_KV_NAMESPACE_ID> config:team-repos '{
"YOUR_TEAM_ID": [
{ "owner": "your-org", "name": "frontend", "label": "frontend" },
{ "environmentId": "env_abc123", "label": "fullstack" },
{ "owner": "your-org", "name": "main-repo" }
]
}'Each team maps to an array of targets — repositories (owner/name) or saved environments
(environmentId, the stable env_… id shown in the web UI). If a target has a label, it only
matches issues with that label. The first target without a label is the default fallback. An
environment entry that is unavailable stops resolution for a bound Open-Inspect team. In workspace
scope, an environment absent from the scoped catalog can be skipped for the next resolution stage; a
failed catalog request still stops resolution rather than using an unscoped fallback.
Project → target mapping:
npx wrangler kv key put --namespace-id <LINEAR_KV_NAMESPACE_ID> config:project-repos '{
"LINEAR_PROJECT_ID": { "owner": "your-org", "name": "my-repo" },
"OTHER_PROJECT_ID": { "environmentId": "env_abc123" }
}'Project mappings take the highest priority during target resolution.
In the Open-Inspect web UI, go to Settings → Integrations → Linear to configure:
- Default model and reasoning effort
- Whether users can override the model via preferences or issue labels
- Whether real-time tool progress activities are shown in Linear
- Which repos the Linear agent is enabled for (allowlist or all)
These can also be set per-repo as overrides.
On any Linear issue:
- Type
@OpenInspectin a comment → agent picks up the issue - Assign the issue to
OpenInspect→ agent picks it up - Agent status is visible directly in Linear (thinking, working, done)
- Add a
model:<name>label to override the model (e.g.,model:opus,model:sonnet,model:opus-5,model:sonnet-5,model:fable-5-1,model:haiku,model:gpt-5.4,model:gpt-6-sol)
When an issue is triggered, the agent resolves the session target using a 5-step cascade:
- Project → target mapping — static mapping from Linear project IDs to a repository or a saved environment (highest priority)
- Team → target mapping — static mapping from Linear team IDs to repositories or saved environments, with optional label filtering
- Explicit
owner/repomention — deterministically selects a single available repository named in the trigger comment or clarification reply - Linear's
issueRepositorySuggestionsAPI — Linear's built-in repo suggestion (>= 70% confidence) - LLM classifier — uses the model selected by
CLASSIFICATION_MODEL(Anthropic by default) to classify based on issue content, labels, and available repo descriptions. Asks the user to clarify if confidence is low.
Environment sessions clone the environment's full repository set; integration settings (model, enabled-repos allowlist) resolve from the environment's primary repository until environment-level settings exist.
All /config/* endpoints require HMAC auth via Authorization: Bearer <token>.
| Endpoint | Method | Description |
|---|---|---|
/health |
GET | Health check |
/webhook |
POST | Linear webhook receiver |
/oauth/authorize |
GET | Start OAuth installation flow |
/oauth/callback |
GET | OAuth callback handler |
/config/team-repos |
GET/PUT | Team → target mapping (repo or environment) |
/config/project-repos |
GET/PUT | Project → target mapping (repo or environment) |
/config/user-prefs/:userId |
GET/PUT | Per-user model and reasoning preferences |
/config/triggers |
GET/PUT | Trigger configuration (legacy) |
/callbacks/start |
POST | Prompt-dispatched callback from control plane |
/callbacks/complete |
POST | Completion callback from control plane |
/callbacks/tool_call |
POST | Tool progress callback from control plane |
The agent uses Linear's native activity system:
| Activity | When | User sees |
|---|---|---|
| Thought | Analyzing issue, resolving repo | Thinking indicator in Linear |
| Response | Session created, PR opened | Comment-like message on the issue |
| Error | Something went wrong | Error message on the issue |
| Action | Tool calls (file edits, commands) | Ephemeral status (e.g., "Editing src/foo.ts") |
| Elicitation | Repo classification is uncertain | Question asking user to clarify |
cd packages/linear-bot
npm install
npm run build
wrangler dev # Local developmentBuilt on Linear's Agents API:
- OAuth2 installation with
actor=app— installs the agent identity in the workspace - OAuth2 client credentials at runtime — mints replaceable 30-day app-actor tokens and renews once after an explicit HTTP 401
- Raw Linear GraphQL API — direct
fetchcalls (no SDK, Workers can't import CJS) - AgentSessionEvent — native trigger when users @mention or assign
- AgentActivity — native status updates visible in Linear's UI
- Issue workflow transition — eligible human-started work may move to the team's lowest-position
startedworkflow state after sandbox dispatch and callback validation; skipped or failed transitions never block execution - Hono for HTTP routing
- KV for the replaceable runtime-token cache, issue-to-session mapping, and configuration
- Service binding to the control plane for session management