feat(linear): agent-session fetch + append-only guards raw GraphQL (chat@4.31/#151 — L5/5)#173
Conversation
…hat@4.31/#151 — L5/5) Port the Linear agent-session FETCH/read path (last of the 5-PR Wave D, #151), building on L1–L4 already on main. The Python adapter has no @linear/sdk, so upstream's linear.agentSession(id) + linear.comments({filter}) calls (fetchAgentSessionMessages, index.ts:1771) are ported as raw GraphQL queries over the existing _graphql_query helper, schema-hardened field-by-field against Linear's published schema.graphql. Surface (strictly the read path; the L4 emit methods and the comment-path fetch are left byte-identical): - fetch_messages: FIRST branch dispatches agent-session threads to the new _fetch_agent_session_messages; comment/issue branches unchanged. - _fetch_agent_session_messages: agentSession(id) query (selecting issue { id } + the root comment relation), issueId/root-comment raises, direction-driven children pagination (forward->first, otherwise last), per-comment thread_id (linear:{issue}:c:{comment}:s:{session}) via reused L4 author logic, and next_cursor = endCursor if hasNextPage else None. - edit_message / delete_message: append-only guards raising the exact upstream AdapterError strings for session threads before any network call. - fetch_thread: agentSessionId added to metadata. CRITICAL schema-hardening: AgentSession has NO scalar issueId field in the published schema (only the issue: Issue relation), so the issue id is read off issue { id } — equivalent to upstream's agentSession.issueId ?? thread.issueId. Requesting a non-existent issueId would server-reject the whole query (the L4 blocking-bug class). Nullish (issue.id ?? thread, endCursor ?? undefined) uses is not None, not or. Tests: tests/test_linear_agent_session_fetch.py (20 tests) — happy path, issueId fallback + missing-issueId/missing-root-comment raises, forward-vs- backward (first/last) pagination, next_cursor by hasNextPage, append-only edit/delete raises, and comment-path-unchanged regressions. Each fails on a plausible mutation (forward/backward swap, per-comment-id collapse, cursor- logic flip, dropped guard, dropped issueId fallback) — all six verified. Doc: UPSTREAM_SYNC L5 divergence row + Wave D marked complete. Wave D (#151) is now complete.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request completes the Linear agent-session support (Wave L5) by implementing the agent-session fetch path. Specifically, it introduces raw GraphQL queries to fetch agent sessions and their child comments, adds append-only guards to edit_message and delete_message to prevent modification of session activities, and implements _fetch_agent_session_messages to handle pagination and parsing of comments into messages. Additionally, it updates the fetch_thread metadata to include agentSessionId and adds a comprehensive test suite in tests/test_linear_agent_session_fetch.py. As there are no review comments, I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…lish on agent-session fetch (L5) - Drop the inbound `after: options.cursor` forwarding from `_fetch_agent_session_messages` and the `$after: String` param from `_AGENT_SESSION_CHILDREN_QUERY`. Upstream `fetchAgentSessionMessages` (index.ts:1793-1804) passes ONLY `first`/`last` and never reads `options.cursor`; the sibling `_fetch_issue_comments`/`_fetch_comment_thread` paths also forward no cursor. `next_cursor` is still returned off `pageInfo.endCursor`. Removes the undocumented divergence; updates the L5 UPSTREAM_SYNC.md row accordingly. - Fix the misleading null-session guard message: when the raw-GraphQL `agentSession(id)` resolves to null (port-only branch — the SDK throws its own not-found upstream), raise "... not found" instead of "... is missing issueId". The separate downstream missing-issueId raise is unchanged. - Add tests pinning: comment-session thread id dispatches to the agent-session fetch (branch-swap mutation); empty-string session issue.id is kept per nullish (`??`/`is not None`) not truthiness (`or`); null-session not-found message; `endCursor` only returned when both hasNextPage and endCursor present. Each new test fails on the corresponding mutation (verified by injection).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 754a58b7f3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if decoded.agent_session_id: | ||
| raise AdapterError( | ||
| "Linear agent session activities are append-only and cannot be edited", | ||
| "linear", | ||
| ) |
There was a problem hiding this comment.
Move the session edit guard before token refresh
When client-credentials auth is configured and the cached token is expired, this branch is reached only after _ensure_valid_token() refreshes the token. If that refresh fails or the token endpoint is unavailable, edit_message() on an agent-session thread raises an auth/network error and performs an unnecessary network request instead of the append-only AdapterError; delete_message() already decodes and guards before auth. Move the decode/session check ahead of token validation for the session case.
Useful? React with 👍 / 👎.
Last of the 5-PR Wave D (#151). Builds on L1–L4 (all on
main). Ports the Linear agent-session FETCH/read path from upstreamadapter-linear/src/index.ts(chat@4.31.0).The Python adapter has no
@linear/sdk, so upstream'slinear.agentSession(id)+linear.comments({filter})calls (fetchAgentSessionMessages, index.ts:1771) are ported as raw GraphQL queries over the existing_graphql_queryhelper, schema-hardened field-by-field against Linear's publishedschema.graphql.Scope (read path only — emit path + comment-path fetch left byte-identical)
fetch_messages: FIRST branch dispatches agent-session threads to the new_fetch_agent_session_messages; thecomment_id/ issue branches are unchanged below it._fetch_agent_session_messages(new):agentSession(id)query →issueId/root-comment raises → direction-driven children pagination → per-comment thread-id parse →next_cursor.edit_message/delete_message: append-only guards raising the exact upstreamAdapterErrorstrings for session threads, before any network call.fetch_thread:agentSessionIdadded to metadata.CRITICAL schema-hardening
AgentSessionhas NO scalarissueIdfield in the published schema (it exposes only theissue: Issuerelation alongsidecomment/sourceComment/id). Upstream'sagentSession.issueIdworks because@linear/sdk's model derives it; in raw GraphQL, requesting a non-existentissueIdwould server-reject the whole query (the L4 blocking-bug class). So the issue id is read offissue { id }— equivalent to upstream'sagentSession.issueId ?? thread.issueId(the sameissueId ?? issue?.idfallback upstream uses at index.ts:959).Other hazards preserved: nullish (
issue.id ?? thread,endCursor ?? undefined) →is not None(NOTor); paginationforward → first/ otherwiselast; per-commentthread_id(linear:{issue}:c:{comment}:s:{session}, NOT a fixed shared id) via reused L4 author logic;is_mention=True.Published-schema confirmation (per field)
agentSession(id: String!): AgentSession!✓ ·AgentSession.{id, comment, issue}✓ (noissueId✓) ·comments(filter: CommentFilter, first/last/after): CommentConnection!✓ ·CommentFilter.parent → NullableCommentFilter.id → IDComparator.eq✓ ·Comment.{id,body,parentId,createdAt,updatedAt,url,user,botActor}✓ ·User.{id,displayName,name,email,avatarUrl}✓ ·ActorBot.{id,name,userDisplayName,avatarUrl}✓ ·PageInfo.{hasNextPage,endCursor}✓.Tests —
tests/test_linear_agent_session_fetch.py(20)Happy path (root+children → per-comment thread-ids, author resolution), issueId fallback + missing-issueId/missing-root-comment raises, forward-vs-backward (first/last) pagination,
next_cursorbyhasNextPage, append-only edit/delete raises, and comment-path-unchanged regressions. Each fails on a plausible mutation (forward/backward swap, per-comment-id collapse, cursor-logic flip, dropped guard, dropped issueId fallback) — all six verified.Gauntlet
ruff check ✓ · ruff format ✓ · audit_test_quality (0 hard failures) ✓ · pyrefly (0 src + 0 test) ✓ · verify_test_fidelity --strict (732/732, 100%) ✓ · pytest (5242 passed, 4 skipped) ✓.
Live-tenant verification pending (documented in
UPSTREAM_SYNC.md): query/field names confirmed against the published schema but not yet exercised against a live Linear agent-session tenant.Wave D (#151) is now complete.