Skip to content

Support Linear agent sessions as a first-class mention surface - #119

Closed
vincelwt wants to merge 3 commits into
getpaseo:mainfrom
vincelwt:feat/linear-agent-sessions
Closed

Support Linear agent sessions as a first-class mention surface#119
vincelwt wants to merge 3 commits into
getpaseo:mainfrom
vincelwt:feat/linear-agent-sessions

Conversation

@vincelwt

@vincelwt vincelwt commented Sep 6, 2026

Copy link
Copy Markdown

What this adds

Linear opens an agent session when a workspace member @mentions or delegates an issue to an installed app. Hub could not receive one: the Linear provider only understood issue and comment webhooks, and its authorization requested neither scope that makes an app mentionable. The practical result was that "mentioning" Paseo meant text-matching @paseo in a comment body, with no session UI, no progress, and a reply that arrived as a loose comment.

This makes Paseo a real Linear agent.

  • AgentSessionEvent deliveries are accepted on the existing signed webhook (payload.agentSession, created and prompted) and routed to a new linear.agent_session trigger.
  • The mention text becomes the prompt, so declared inputs parse straight from it and the existing named-agent select expression picks the agent: @Paseo agent=fast summarize this runs the fast choice.
  • Hub narrates the run back as Linear activities: a thought on pickup, an external link to the run, the agent's own reply as the session response, and an error activity carrying the failure reason.
  • ${{ paseo.context }} uses Linear's rendered promptContext, so session runs no longer backfill comment history through the API.

Notable decisions

No actor allowlist for sessions. validateTriggerLaunchSecurity requires a non-empty filters.from_users for externally sourced events. A session is exempt: Linear creates one only when a member addresses this app by name, delivers it to no other app, and the install already scoped which teams can see it. Requiring an allowlist would make the documented mention flow impossible to configure. Project, label, state, and actor filters all still apply when set.

Scopes are requested, not required. agentActivityCreate is rejected with Invalid scope: `write` required under the narrow comments:create grant, so write joins app:mentionable and app:assignable. Adding them to LINEAR_REQUIRED_SCOPES would flip every existing connection to requiresReauthorization and silently drop its issue and comment events, so they live in a separate LINEAR_AGENT_SCOPES set instead. Existing workspaces keep working and only miss mentions until they reconnect.

One response per session. Linear ends a session when it sees a response. The agent's own reply is the good one, so a small in-process tracker records that it arrived and the terminal hook only fills a gap rather than talking over it. In-memory is deliberate: a restart mid-run costs at most one duplicate closing line.

Activity failures never fail a run. Every emission is wrapped and reported; a session that cannot be narrated still runs to completion.

Linear answers a malformed GraphQL document with a bare 400, so the response body is now included in the thrown error. Without it every schema mismatch looked identical in the logs, which is how the write scope requirement above was found.

Verification

Unit tests cover normalization of both actions, webhook routing and signature rejection, filter behaviour including connection scoping, prompt and input parsing, context materialization without API reads, activity emission and its tolerance of Linear failures, and the session-aware reply path.

Also exercised end to end against a real Linear workspace on a self-hosted Hub:

  • @Paseo appears in Linear's mention menu and opens a session.
  • The session leaves its unresponsive state, shows the Hub link, and completes with the agent's answer.
  • A failing run surfaces as an error activity with the reason.
  • @Paseo agent=fast Reply with only your model name answered claude-haiku-4-5 while the trigger default is claude-opus-5, confirming per-mention agent selection.

Not included

Follow-up prompted events currently start a fresh execution rather than continuing the live agent, because the Hub to daemon protocol has no prompt message (HubExecutionControlActionSchema is interrupt and archive only). Conversational sessions and mapping permission_requested to an elicitation both depend on that addition and are better as a separate change spanning both repositories.

🤖 Generated with Claude Code

Linear creates an agent session when a workspace member @mentions or
delegates an issue to an installed app. Hub could not receive one: the
Linear provider only understood issue and comment webhooks, and its
authorization asked for neither of the scopes that make an app
mentionable.

Accept `AgentSessionEvent` deliveries on the existing signed webhook and
route them to a new `linear.agent_session` trigger. A session is
addressed to this app by name and delivered to no other app, so the
mention itself is the authorization and the trigger is exempt from the
actor allowlist that externally sourced events otherwise require;
project, label, and actor filters still narrow scope when a workspace
routes different work to different repositories.

The mention text becomes the prompt, so declared inputs are parsed
straight from it and an existing named-agent `select` expression can
choose which agent runs: `@Paseo agent=fast summarize this` runs the
`fast` choice.

Hub narrates the run back into the session as Linear activities: a
thought on pickup, which is what keeps Linear from marking the session
unresponsive within its ten second window, an external link back to the
run, the agent's own reply as the session response, and an error
activity carrying the failure reason. Activity failures are reported but
never fail the run.

`agentActivityCreate` is rejected under the narrow `comments:create`
grant, so `write` joins `app:mentionable` and `app:assignable` in the
requested scopes. They are requested but not required: a workspace
connected before agent sessions keeps its issue and comment triggers and
only misses mentions until it reconnects.

Linear answers a malformed GraphQL document with a bare 400, so the
response body is now included in the thrown error. Without it every
schema mismatch is indistinguishable in the logs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

A reply in an existing Linear session is a prompted event. Hub was
starting a fresh agent for each one, so the thread in Linear looked
connected while the coding agent did not.

Prompted follow-ups now look up the live execution for that session and
send the new request as a prompt on the same daemon agent. finish_execution
ends the turn but keeps the session open until idle timeout. If no live
agent remains, Hub still starts a new one.
Pi has no session modes. Hub still required mode on new triggers, so
linear-mention had to send mode: default, and the daemon rejected spawn
with "Invalid mode 'default' for provider 'pi'".

Authoring now lets mode be omitted; the daemon remains the source of
truth for providers that do require one.
@vincelwt

vincelwt commented Sep 7, 2026

Copy link
Copy Markdown
Author

Closing: this is private Hub work, not for upstream.

@vincelwt vincelwt closed this Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant