fix(linear): stable agent-session thread ids, rootless sessions, undetermined comment mentions (#232) - #280
Conversation
…termined comment mentions (#232) Ports vercel/chat 3d2cb22a (#885, chat@4.40.0) and the Linear half of fcdc1c9e (#946, chat@4.41.0). - Every agent-session message uses linear:{issue}:s:{session} (webhook created/prompted, fetch, emit). Old :c:{comment}:s: ids still decode. - Prompts without a source comment use the activity id; sessions created without a root comment are dispatched with a synthetic comment built from the prompt context; creator-less sessions are authored by a distinct 'Linear automation' bot instead of the bot itself. - fetch_messages on a rootless session reads agentSession.activities (first/after forward, last/before otherwise) via a schema-checked raw GraphQL query, rendering actions like upstream renderActivity. - parse_message delegates the agent_session_comment kind; ordinary comments leave is_mention unset.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 13 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (8)
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 |
…tedAt/email, empty promptContext; document parse_message ordinary-comment gap (#285)
|
@coderabbitai review |
|
|
Merge gate: CI green (test 3.12, test 3.13, Lint & Type Check, CodeQL, Analyze python/actions) on 2efc1b5; local Codex review (gpt-6-astra, xhigh, --base origin/main) on 13b7017: "No actionable regressions found. All 303 Linear tests passed, targeted lint checks passed, and the new GraphQL selections match Linear's published schema. Live-tenant behavior was not verified."; 2 astra rounds (both clean). 2efc1b5 is a clean, conflict-free merge of origin/main (#272/#229/#273/#281, none touching Linear); the PR's src/tests/scripts delta is line-identical to the reviewed 13b7017, so no re-review was needed. Local full validation green (6884 passed, strict fidelity, pyrefly 0 errors; fidelity target unchanged at 151 missing). CodeRabbit rate-limited (no review). Merging with --admin (Protect Main requires a code-owner approval). |
Summary
Linear agent sessions now route every event to one stable thread,
linear:{issueId}:s:{agentSessionId}. Before this change, each source comment produced its own…:c:{comment}:s:{session}thread.created: a session without a root comment is dispatched with a synthetic comment{id: "agent-session-{sessionId}", body: promptContext ?? ""}. It used to be dropped. A session with no creator is authored by{type: "bot", id: "linear-automation", displayName/fullName: "Linear automation"}. It used to be authored as the bot itself, which madeis_metrue, so core dropped it as a self-message.prompted: a prompt withoutsourceCommentIdusesagentActivity.idas the message id. It used to be dropped with "Missing source comment ID"._fetch_agent_session_messagesnow calls the new_fetch_agent_session_activitiesinstead of raisingAdapterError("… missing a root comment"). That method pagesagentSession.activities(first/afterforward,last/beforeotherwise, default 50), sorts by parsedcreatedAtand renders each activity with_render_activity. Prompts are authored as the user and every other activity as the bot.next_cursorisendCursor(forward +hasNextPage) orstartCursor(backward +hasPreviousPage).post_message/streamreturn the stable session thread id.parse_message: theagent_session_commentkind delegates to_parse_agent_session_message(is_mention=True, stable thread). Ordinary comments leaveis_mentionunset (None), so core@mentiontext detection from [4.41/C2a] Core mentions & message model: tri-state is_mention, mention regex, Author.email/is_system, Message.reply_to #192 still runs on them.decode_thread_id/encode_thread_idare unchanged. The old:c:{c}:s:{s}form still decodes, so posting to a stored old id still reaches its session.Upstream commits mapped
3d2cb22afix(linear): stabilize agent session event threads (#885, chat@4.40.0)parseMessagestable id →_parse_agent_session_message, emit_raw_message_from_source_comment. 2.sourceCommentId ?? activity.id→ prompted branch. 3. Synthetic root comment → created branch. 4.linear-automationauthor → created branch. 5.fetchAgentSessionActivities+renderActivity→_fetch_agent_session_activities+_AGENT_SESSION_ACTIVITIES_QUERY+_render_activity.fcdc1c9efix(chat): honor adapter-reported non-mentions (#946, chat@4.41.0), Linear halfparse_message:Truefor agent-session comments, unset for ordinary comments. The core tri-state landed in #192.Verify-first: Linear schema (raw GraphQL)
I checked the activities query field by field against Linear's published
schema.graphql, the same copy used to harden L4/L5 (#231):AgentSession.activities(after: String, before: String, first: Int, last: Int, …): AgentActivityConnection!AgentActivityhas no scalarsourceCommentIdoruserId. Only the relationssourceComment: Commentanduser: User!exist; the SDK derives the ids from them. The query therefore selectssourceComment { id }anduser { id displayName name email avatarUrl }inline, in one query with no per-activity follow-ups.content: AgentActivityContent!is a union ofAgentActivity{Prompt,Response,Thought,Error,Elicitation}Content(each hastype,body: String!) andAgentActivityActionContent(type,action: String!,parameter: String!,result: String). The query selects these with inline fragments.AgentActivityTypevalues are lowercase (prompt, …).PageInfohashasNextPage,hasPreviousPage,endCursorandstartCursor.AgentSession.url: Stringwas added to the session query; the activities path copies it onto each message, as upstream does.Tests ported
packages/adapter-linear/src/index.test.tsis not fidelity-mapped (#78). Ported by name:tests/test_linear_webhook.py: "routes prompted events to the same stable session thread", "routes prompted events without a source comment", "uses an automation author when a created session has no creator", "dispatches created sessions without a root comment". The upstream payload fixture'scomment: nulloverride and the activitysourceCommentId/userwere mirrored.tests/test_linear_agent_session_fetch.py: "should fetch activities for agent sessions without a root comment" ({id, last: 50}, textsUser prompt/Searching: Chat SDK\nFound documentation/Agent response). Also theissue-public+comment: nullcase of theit.each"validates agent session ownership through the Linear SDK", which now resolves tomessages=[],next_cursor=Nonethrough the activities query with two requests over the real_graphql_query.tests/test_linear_adapter.py: "should mark an agent session comment as a mention", "should leave isMention undetermined for an ordinary comment".Python-specific tests:
after/before), omission of an empty cursor, and the next cursor per direction (parametrized)._render_activity: an action that is empty after trimming renders"Action"; a blank parameter or result is dropped; the JS.trim()set strips U+3000 and the BOM but keeps U+001C and U+0085.user(prompt →"unknown", other → bot id anduser_name), andsourceComment.id ?? activity.id.linear:{i}:c:{c}:s:{s}thread id still posts to its session (emit tests).Existing tests that asserted the per-comment session thread id were updated in place: the fetch happy path, the dispatch-ordering test, the webhook created/routed/nested-issue tests, and the emit post/stream tests. The tests for removed drop paths ("Missing source comment ID", "Missing comment for agent session", "missing a root comment") were replaced by the ported tests above.
15 targeted mutations of the new code were all caught by the suite (str.strip vs JS trim, the
"Action"fallback,after↔before, an unforwarded cursor, no sort, invertedis_bot, the wrong next cursor per direction, the automation id, dropping prompted/created events again, noparse_messagedelegation, ignoringsourceComment, raising on rootless sessions, the display-name fallback).Fidelity
TS_ROOT=…/vercel-chat-4.41.1 uv run python scripts/verify_test_fidelity.py --report-target:The count is unchanged because
adapter-linearis not mapped (#78), soscripts/fidelity_target.jsonis unchanged.Divergences
None new. The activities query sends only the variables upstream passes (
{id, last: 50}by default), so unused bounds are omitted rather than sent asnull. That matches the SDK call. TheUPSTREAM_SYNC.mdL4 and L5 rows are updated (stable ids, activities query), and so is the #192 mention section's Linear note.Consumer impact
linear:{i}:c:{c}:s:{s}tolinear:{i}:s:{s}. Subscriptions and state keyed by old ids will not match new events. Re-subscribe, or map stored ids by dropping the:c:{comment}segment. Posting to a stored old id still works. A side effect consumers will see: a thread subscribed on thecreatedevent now receives the follow-uppromptedevents throughon_subscribed_message, because they share the thread.is_mentionstaysNone, so text detection applies. This matches the behavior before [4.41/C2a] Core mentions & message model: tri-state is_mention, mention regex, Author.email/is_system, Message.reply_to #192 for these messages.Validation
Full validation (ruff check, ruff format --check, audit_test_quality,
verify_test_fidelity.py --check-docs, strict fidelity at 4.31.0 733/733, pytest) is green:6636 passed, 24 skipped.pyrefly check: 0 errors.Closes #232
Part of #184
Review
gpt-6-astra Codex review round 1 on
89b35dd: PASS ("No actionable regressions found ... the new GraphQL selections match Linear's published schema."). CI on the draft: test (3.12/3.13), CodeQL green; Lint & Type Check runs once the PR is out of draft (ruff, audit, fidelity --check-docs/--strict and pyrefly were run locally, all green).Merge gate
Final HEAD:
13b7017. This is6a26365, which adds the review test fixes, plus a merge oforigin/main. The merge was clean andfidelity_target.jsondid not conflict.Independent review (4 findings, 3 fixed and 1 handled as documentation):
is_mention is Noneon the paths core routes. Two assertions were added: one to the comment-mode webhook dispatch test (test_linear_webhook.py::test_comment_still_dispatched_in_comment_mode) and one to the comment-thread fetch test (test_linear_extended.py::test_validates_comment_ownership_through_the_transport). The mutationis_mention=Falseis now caught at both_build_messageand_comment_node_to_message.??chains were not pinned. The ported activities test now assertsuser_name/full_name == ("Alice", "Alice Smith")and the rawemail. It also gives the prompt activity a distinctupdatedAtand assertsedited == [True, False, False]. The mutations displayName↔name, updatedAt-from-createdAt and dropped-email are all caught.promptContext ?? ""fallback was not pinned.test_dispatches_created_sessions_without_a_root_commentis now parametrized with a no-promptContextcase that assertstext == "", and thebody: Nonemutation is caught.parse_messagereturnsthread_id=""and an"unknown"author. This gap predates the PR and is not on a routed path: core never callsparse_message. The docstring is narrowed so the parity claim covers only the agent-session branch. A known-divergence row was added todocs/UPSTREAM_SYNC.md(Platform-specific gaps), and follow-up Linear: parse_message for ordinary comments returns empty thread id and unknown author (upstream parity) #285 was opened. No sibling issue under [4.41] Tracking: sync upstream chat@4.31.0 → chat@4.41.1 (0.4.41 wave) #184 owned it.gpt-6-astra: 1 round on
13b7017, verdict PASS ("No actionable regressions found. All 303 Linear tests passed, targeted lint checks passed, and the new GraphQL selections match Linear's published schema. Live-tenant behavior was not verified."). The earlier draft round on89b35ddwas also PASS.Bots: CodeRabbit skipped the draft. A manual
@coderabbitai reviewafter ready-for-review came back as "Review rate limited", so there are no CodeRabbit or gemini review comments to address.CI on
13b7017: test (3.12), test (3.13), CodeQL and Analyze (actions/python) are green. The PR-triggered Lint & Type Check was skipped because the push landed while the PR was still a draft (the ready_for_review run was cancelled by concurrency). Aworkflow_dispatchLint & Type Check run on the same SHA was green: https://github.com/Chinchill-AI/chat-sdk-python/actions/runs/36820135415.Local full validation on
13b7017: ruff check, ruff format --check, audit_test_quality (0 hard failures),verify_test_fidelity.py --check-docs, strict fidelity at 4.31.0 (all TS tests matched) and pytest (6689 passed, 24 skipped) all pass.pyrefly checkreports 0 errors.--report-targetat 4.41.1 givesDelta vs committed report (HEAD): missing 167 -> 167 (+0), sofidelity_target.jsonis unchanged.