Repository navigation
Conversation
Before a source's first run (no source cursor), start a cursor on it for every consumer that already has one, at its newest message. Without it consumer_has_unread ignores the new source, so the inbox-processor's messages gate never wakes for it, and the first poll starts past its first records (this already affects github_repos).
…'s MCP The agent acts as a regular Linear user (service account or personal API key). sync.linear polls its issue notifications (assigned, mentioned, comments) by createdAt, never marking anything read, and drops the agent's own actions, bots (assignments exempt), denied actors and filtered teams before storage; the runner guardrail repeats the settings. With mcp on, Linear's hosted MCP server is registered as "linear" with the same key, for startup and reload alike. Doctor, onboarding facts, TOOLS.md, docs, example config and a Sources page renderer come with it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Nerve agents can't work with Linear yet. They should pick up issues assigned to them, react to mentions and comments, and write back (comment, change status or assignee).
The agent acts as a regular Linear user, through a service account or a personal API key, not as a native Linear agent app. An agent app would need webhooks, agent sessions, an OAuth install and a 10-second ack, and Nerve has no public webhook receiver. This PR is self-hosted only: triggers are set in Nerve's config, and the hosted contract and nerve-private are untouched. Records are shaped so that a hosted variant can be added later without changing them.
Building this exposed a generic bug, fixed in its own commit: a new source never woke the
inbox-processor.consumer_has_unreadignores sources without a consumer cursor, andget_consumer_cursorthen starts the cursor past the source's first records. This already affectsgithub_repos(#593).What
1. New sources reach existing consumers (
nerve/db/sources.py,nerve/sources/runner.py)Database.seed_consumer_cursors(source): before a source's first run (no source cursor), every consumer that has a cursor elsewhere gets one on the new source at its currentMAX(rowid).2.
sync.linearsource (nerve/sources/linear.py, modelled ongithub_repos, #593 / #594)notificationsover GraphQL withhttpx.typesmaps to Linear notification types:assigned→issueAssignedToYoumentioned→issueMention,issueCommentMentioncomments→issueNewCommentcreatedAt, becauseupdatedAtmoves on read and when the reminder e-mail goes out. It is JSON with{since, at, listed_at, viewer};atbreaks ties within the same millisecond.include_bots; assignments always pass),deny_actors, andallow_teams/deny_teams.linear_inbox_filterrepeats these rules as the runner guardrail.RATELIMITED, GraphQL errors returned with HTTP 200, auth failures and timeouts all raise. The runner then keeps the cursor and backs off.3. Writes go through Linear's official MCP server
_parse_mcp_serversadds built-in servers. Withsync.linearenabled,mcp: trueand a key, it registerslinear(https://mcp.linear.app/mcp,Authorization: Bearer <key>).load_mcp_servers(mcp_reload) get their servers from, so a reload adds or removes the server.mcp_servers.linearwins. The key never reaches the DB, and Codex receives it throughenv_http_headers.Public config (new, everything off by default and follows a reload):
sync.linear.api_keyis added to the setup provenance credential paths.("sync", "linear")is added to the effectful settings keys, because it registers an MCP server.Surfaces
nerve doctor: a Linear line covering off, no key, types, schedule and MCP status. It warns in external auth mode, which ignores configured MCP servers.TOOLS.mdtemplates:linearin the source list, plus a line onmcp__linear__*.LinearRenderershowing identifier, team, event and state badges, with a "View in Linear" link.docs/sources.md,docs/config.md,docs/worker-guide.md(alinear-tasksjob for workers, which have noinbox-processor), andconfig.example.yaml.Manual verification (live Linear workspace)
Prerequisites: a test Linear workspace, a service-account user with a personal API key exported as
LINEAR_API_KEY, and a second (human) user to assign, mention and comment.Configure. In
config.local.yaml:Doctor. Run
nerve doctor. Expect[OK] Linear source: types assigned, mentioned, comments; schedule */5 * * * *; MCP registered as linear, and no Linear warning.Baseline. Run
nerve sync lineartwice. The first run takes a baseline and stores nothing; the second reports nothing new.Triggers. As the human user, on a test issue:
[ID] Assigned to you by <name>: <title>[ID] <name> mentioned you: <title>[ID] <name> commented: <title>Run
nerve sync linear. Expect three records with these summaries (Sources page →linear, rendered with the Linear card).Dedupe. Post one comment that mentions the service account → one record (
mentioned), not two. Post two plain comments → two records.Self-filter. In a chat, run
mcp_reload, thenmcp__linear__get_issueon the test issue (it should work), thenmcp__linear__save_commenton it. Afternerve sync linear, expect no record for that comment.Inbox processing. Personal mode: the next
inbox-processorrun picks up the records viapoll_all_sources. Worker mode: add thelinear-tasksjob fromdocs/worker-guide.md, assign an issue, and check that the agent comments, creates a task and moves the issue to In Progress.Off again. Set
enabled: falseand reload.nerve doctorshows the source off, thesource:linearjob is unscheduled, and aftermcp_reloadthelinearMCP server is gone.