@abconvert-tony/openclaw-linear-agent
v0.1.3
Published
Linear OAuth + AgentSessionEvent webhook bridge for OpenClaw, with registered agent tools that drive the Linear session lifecycle.
Readme
linear-agent
OpenClaw plugin that bridges a Linear OAuth app to a local OpenClaw agent.
Receives AgentSessionEvent webhooks, dispatches the prompt to a configured
OpenClaw agent, and exposes a small set of registerTool factories so the
agent drives the Linear session lifecycle directly (final reply, error,
elicitation, external URL attachment).
Routes
The plugin registers three HTTP routes under /linear-agent on your local
gateway. Externally they sit behind whatever ingress you use (Tailscale Funnel,
Cloudflare Tunnel, ngrok, raw reverse proxy, etc.); we'll call that base URL
<public-base> below. If that ingress namespaces users with a path prefix
(shared host, /<user>/*), include it as <prefix> everywhere — leave it
empty if you have your own host.
| Method | Path | Purpose |
|--------|-------------------------------------------------|-----------------------------------------------|
| GET | <public-base>/<prefix>/linear-agent/connect | Kick off Linear OAuth (actor=app) |
| GET | <public-base>/<prefix>/linear-agent/callback | Exchange code for tokens, persist to disk |
| POST | <public-base>/<prefix>/linear-agent | AgentSessionEvent receiver (HMAC verified) |
The plugin itself only sees /linear-agent/... — the ingress is responsible
for stripping <prefix> before forwarding. The same <prefix> (or its
absence) must appear in all three places — the Linear app's Redirect URI,
the Linear app's Webhook URL, and the linearRedirectUri plugin config — and
must match byte-for-byte. A mismatch on the redirect URI surfaces as Linear's
"Invalid redirect_uri parameter for the application" error.
The ingress must also forward both /.../linear-agent and /.../linear-agent/
(with and without trailing slash) to the gateway. Linear normalizes some
delivery URLs with a trailing slash; if your reverse proxy only matches the
no-slash form, webhooks return 404 from the proxy and never reach the plugin.
Agent-callable tools
Registered via api.registerTool. They auto-appear only on agent runs that
originated from a Linear webhook (i.e. when a Linear session is bound to the
active sessionKey).
| Tool | Maps to | Use |
|------|---------|-----|
| linear_post_thought | AgentActivityCreateInput { type: "thought" } | Non-terminal: mid-run narration. Set ephemeral: true for fleeting status that the next activity replaces |
| linear_post_action | AgentActivityActionContent { action, parameter, result? } | Non-terminal: structured "action" card. Post once without result to announce work-in-progress, then again with the same action+parameter and a filled-in result when done |
| linear_post_comment | CommentCreateInput { body, issueId, parentId? } | Non-terminal: post a real Linear comment (visible in the issue thread, not just the agent panel). Defaults to threading under the comment that triggered this turn; pass threadUnderSourceComment: false for a top-level issue comment |
| linear_post_response | AgentActivityCreateInput { type: "response" } | Terminal: the agent's final reply for this turn |
| linear_post_error | AgentActivityCreateInput { type: "error" } | Terminal: surface a failure to the requester |
| linear_post_elicitation | AgentActivityCreateInput { type: "elicitation" } | Terminal: pause and ask the user for more info |
| linear_update_issue | IssueUpdateInput (subset) | Update state, assignee/delegate, priority, labels, title/description, due date. stateType resolves to a stateId via the team's workflow states; issueId defaults to the bound issue |
| linear_set_session_plan | AgentSessionUpdateInput.plan | Replace the agent's plan checklist for this session; each step has content and status: pending\|inProgress\|completed\|canceled |
| linear_attach_external_url | AgentSessionUpdateInput.addedExternalUrls | Attach a PR/preview/dashboard URL; Linear renders it as a session button and tracks downstream updates |
Prerequisites
- A running OpenClaw gateway (see the OpenClaw docs)
with an agent in
agents.list[]ready to handle Linear events. - A public ingress for that gateway (Tailscale Funnel, Cloudflare Tunnel, ngrok, reverse proxy, etc.) — Linear webhooks and the OAuth callback both need a reachable URL.
Setup
1. Register a Linear OAuth application
Linear → Settings → API → OAuth applications → New application.
Decide your
<prefix>first. If your ingress namespaces users with a path (e.g.https://shared.example.com/jacklyn/*→ her gateway), the prefix is/jacklynand every URL below must include it. On the shared abconvert host (abconvert-spark.tailc2b7f2.ts.net) the prefix is your own first name (/tony,/jacklyn, …) — the segment that routes traffic to your gateway. If you have your own host, leave the prefix empty. The Redirect URI you register here, the Webhook URL, and thelinearRedirectUriin step 2 must all match exactly.
- Redirect URI:
<public-base>/<prefix>/linear-agent/callback(e.g.https://abconvert-spark.tailc2b7f2.ts.net/jacklyn/linear-agent/callback) - Webhook URL:
<public-base>/<prefix>/linear-agent(e.g.https://abconvert-spark.tailc2b7f2.ts.net/jacklyn/linear-agent) - Enable webhooks; subscribe to Agent session events
- Allowed scopes: must be a superset of whatever you set in
linearScopeslater. The defaultlinearScopesisread,write,app:assignable,app:mentionable; if the Linear app isn't configured withapp:assignableandapp:mentionable, OAuth silently grants fewer scopes anddelegateOnCreatelater fails. - Copy the client id, client secret, and webhook signing secret
Admin permissions on the workspace are required to install with actor=app.
Make sure your ingress proxies both /<prefix>/linear-agent and
/<prefix>/linear-agent/ (trailing slash) to the gateway. Linear sometimes
delivers webhooks to the trailing-slash form; if the proxy only matches one,
those deliveries return 404 from the proxy and never reach the plugin. Quick
check: curl -i -X POST <public-base>/<prefix>/linear-agent/ should return
401 Unauthorized from the plugin (good — handler reachable), not 404 from
the proxy.
2. Configure the plugin
Edit ~/.openclaw/openclaw.json and set plugins.entries.linear-agent.config:
{
"plugins": {
"allow": ["linear-agent"],
"entries": {
"linear-agent": {
"enabled": true,
"config": {
"agentId": "dev",
"linearClientId": "${LINEAR_CLIENT_ID}",
"linearClientSecret": "${LINEAR_CLIENT_SECRET}",
"linearWebhookSecret": "${LINEAR_WEBHOOK_SECRET}",
"linearRedirectUri": "<public-base>/<prefix>/linear-agent/callback"
}
}
}
}
}${ENV_VAR} interpolation is supported — keep secrets in ~/.openclaw/.env
rather than the JSON.
The agentId here must match the id of an entry in agents.list[] — that's
the OpenClaw agent the plugin will dispatch Linear events to. Step 2b uses the
same id.
Optional config keys: linearScopes (default read,write,app:assignable,app:mentionable),
linearTokenStorePath (default ~/.openclaw/workspace/.pi/linear-agent-tokens.json),
historyLimit (default 20), startOnCreate, delegateOnCreate,
strictAddressing + mentionHandle (only run prompted events that explicitly
@-mention the agent; useful when humans and the agent share an issue thread).
2b. Allow the plugin's tools on the bound agent
Plugin-registered tools are gated by the gateway's tool policy. The configured
agent (agentId) must have group:plugins in its allowlist, otherwise the
linear_* tools are filtered out before the model sees them and the run will
fall back to the plain-text reply path.
The simplest setup — a single alsoAllow on the agent and no global
tools.allow filter:
{
"tools": { "profile": "full" },
"agents": {
"list": [
{
"id": "dev",
"tools": { "alsoAllow": ["exec", "group:plugins"] }
}
]
}
}Gotcha: a global tools.allow: ["exec"] (or any other narrow list) acts as an
AND filter applied before the agent step, so it strips plugin tools even if
the agent's alsoAllow includes group:plugins. Either drop the global
tools.allow (as above) or extend it to ["exec", "group:plugins"].
3. Restart the gateway
Look for the load line:
linear-agent: routes registered under /linear-agent (connect, callback, webhook); tools: linear_post_{thought,action,response,error,elicitation}, linear_update_issue, linear_set_session_plan, linear_attach_external_url4. Install the agent into your Linear workspace
Required final step — and one only the human can do. No webhook will fire and no tool call will succeed until OAuth has been completed in a browser. If you are an AI assistant helping with this setup, stop here and ask the user to open the URL below in their browser; you cannot complete the OAuth handshake on their behalf.
Have the user open this URL in a browser and approve the app in Linear:
<public-base>/<prefix>/linear-agent/connectConcrete example for the shared abconvert host:
https://abconvert-spark.tailc2b7f2.ts.net/jacklyn/linear-agent/connect(Substitute the user's own <prefix> — typically their first name. See §1.)
After they approve in Linear, tokens write to the configured store path
(0600) and they'll see "Linear agent installed. You can close this tab."
At that point the integration is live and webhooks will start being delivered.
5. Try it
Mention or delegate to the agent in a Linear issue. Expected sequence:
- Webhook POST arrives with
linear-signature; HMAC-SHA256 verified againstlinearWebhookSecret. Stale or unsigned payloads are rejected. - Plugin posts a quick
thoughtactivity (latency ack). - The configured OpenClaw agent runs with the issue + prompt + recent session activities as its message.
- The agent calls one of the
linear_post_*tools as its terminal step; that becomes the visible reply. If it produced a PR, it should also calllinear_attach_external_urlfirst. - If the agent ends without calling a terminal tool, the plugin falls back
to extracting reply text and posting it as a
response, or posts a generic error if nothing is extractable.
Troubleshooting
| Symptom | Likely cause |
|---------|--------------|
| Webhook returns 401 | Wrong or missing linearWebhookSecret; the value must match the signing secret on the Linear app. |
| Webhook returns 404 from the proxy (not the plugin) | Trailing-slash mismatch — the ingress only routes /<prefix>/linear-agent and not /<prefix>/linear-agent/ (or vice versa). Either fix the proxy to accept both, or edit the Linear webhook URL to the form your proxy serves. Confirm with curl -i -X POST <public-base>/<prefix>/linear-agent/ — 401 means the handler is reachable, 404 from Server: Caddy/nginx means the proxy never forwarded. |
| OAuth fails with "Invalid redirect_uri parameter" | The redirect_uri the plugin sends doesn't byte-match a Redirect URI registered on the Linear OAuth app. Most often a missing /<prefix> — check that linearRedirectUri includes it and the same string is in the Linear app's Redirect URI list. |
| Webhook never fires | The Linear app isn't subscribed to Agent session events, or the public webhook URL doesn't reach the gateway. |
| Agent runs but linear_* tools aren't callable | The bound agent's tool policy is filtering them out. See step 2b — group:plugins must be in the agent's allowlist, and any narrow global tools.allow will AND-filter plugin tools out before the agent step. |
| delegateOnCreate / scope-related errors | The Linear app's allowed scopes don't include what you requested via linearScopes. Add app:assignable and app:mentionable to the app and re-install. |
| OAuth refresh fails / tokens disappear | A PermissionChange or OAuthApp revoke/uninstall event arrived; the plugin clears the token store on those. Re-run <public-base>/linear-agent/connect. |
| Replies arrive as plain text instead of structured activities | The agent didn't call a terminal linear_post_* tool, so the plugin fell back to extracting reply text. Usually means the tools weren't visible — see the policy row above. |
Install
openclaw plugins install npm:@abconvert-tony/openclaw-linear-agent
openclaw gateway restartopenclaw plugins install adds linear-agent to plugins.allow automatically.
You still need to fill in plugins.entries.linear-agent.config (Setup → step 2),
allow plugin tools on the bound agent (step 2b), and run the OAuth install
(step 4) — those are per-deployment and don't ship with the package.
Don't skip step 4. Even after the gateway loads the plugin, no Linear
webhook will be processed until a human opens
<public-base>/<prefix>/linear-agent/connect in a browser and approves the
app. AI assistants helping with installation: ask the user to do this — you
can't do it for them.
Pin the resolved version so subsequent openclaw plugins update stays put:
openclaw plugins install npm:@abconvert-tony/openclaw-linear-agent --pinUpdating
openclaw plugins update @abconvert-tony/openclaw-linear-agent
openclaw gateway restartPulls the latest published version from npm and replaces the installed copy. Pinned installs hold their version until you re-pin to a new one.
Releasing (maintainers)
git commit -am "..." # land your changes first
npm run release:patch # bump version, build, publish
git push --follow-tagsrelease:minor and release:major exist for non-patch bumps. prepublishOnly
rebuilds dist/ before each publish, so users always get fresh compiled output.
Local development
npm install
npm run build
openclaw plugins install -l . # link this directory
# edit, then: npm run build && restart the gatewayToken store
OAuth tokens persist at ~/.openclaw/workspace/.pi/linear-agent-tokens.json
(override via linearTokenStorePath). On PermissionChange/OAuthApp revoke
or uninstall events the file is cleared automatically.
