feat(composio): add Linear provider for Memory Tree ingest#2402
Conversation
Mirrors the existing gmail / notion / slack / clickup ComposioProvider
layout to periodically ingest the connected user's Linear issues into
the Memory Tree. Until now Linear was exposed only as a catalog-only
toolkit (curated for tool-calling but never reaching long-term memory).
Implementation follows the ClickUp provider's incremental-sync model
1:1 (the most recent native provider, structurally simplest reference):
1. SyncState load + daily request budget check.
2. Resolve the authenticated viewer's id via LINEAR_GET_VIEWER so we
can scope the filter by `assignee`.
3. Re-check budget after the viewer probe — same discipline
ClickUpProvider got after the CodeRabbit feedback on tinyhumansai#2291
between its user-id and workspaces probes, so the daily cap is
honoured strictly even when entering the sync with one slot left.
4. Page through LINEAR_LIST_LINEAR_ISSUES filtered by `assignee = viewer_id`,
sorted by `updatedAt` descending. Stop each pass early when a
task's updatedAt is older than the saved cursor or when Linear's
Relay pagination signals `pageInfo.hasNextPage == false`.
5. Per issue, persist as one memory document via the shared
`persist_single_item` helper. Dedupe by composite
`issue_id@updatedAt` so edited issues re-ingest.
6. Advance cursor to newest updatedAt seen across the pass, record
last_sync_at_ms, save state.
Privacy posture: only issues the user is assigned to are pulled,
never the whole workspace's issue graph. Matches the
"fetch-what-the-user-sees" stance gmail / notion / clickup already
take and avoids accidentally ingesting other teammates' private work.
Files added:
- composio/providers/linear/mod.rs — module wiring (24)
- composio/providers/linear/provider.rs — LinearProvider impl (~450)
- composio/providers/linear/sync.rs — payload helpers (~290)
- composio/providers/linear/tools.rs — LINEAR_CURATED, 22 actions (~115)
- composio/providers/linear/tests.rs — 18 unit tests (~165)
Files modified (registration touchpoints):
- composio/providers/mod.rs — pub mod linear; +
has_native_provider arm +
native_provider_sync_interval arm +
catalog_for_toolkit("linear") repointed +
new regression test
`capability_matrix_includes_linear_as_native_memory_provider`
- composio/providers/registry.rs — register_provider in
init_default_providers
- composio/providers/descriptions.rs — Linear description updated
to mention Memory Tree sync
- composio/providers/catalogs.rs — LINEAR_CURATED re-export
removed (now in linear::)
- composio/providers/catalogs_productivity.rs — LINEAR_CURATED const
removed (migrated to linear::tools)
Tests: 38 new linear unit tests pass (helpers, trait metadata, capability
registration). All 303 existing `composio::providers` tests continue to
pass — no regression on gmail / notion / slack / clickup or any of the
catalog-only toolkits.
Verified locally:
- `cargo check --lib` clean (pre-existing warnings only)
- `cargo test --lib composio::providers` 303/303 pass
- `cargo test --lib composio::providers::linear` 38/38 pass
- `cargo test --lib capability_matrix_includes_linear` 1/1 pass
- `cargo fmt --check` clean
- `cargo clippy --lib --no-deps` no new warnings in
`src/openhuman/composio/providers/linear/`
Closes tinyhumansai#2400
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR consolidates the Linear provider's curated-tools catalog from duplicate declarations in catalogs_productivity into the provider module, registers linear as a native Composio provider with proper sync intervals and catalog routing, and adds German UI translations for MCP Server configuration. ChangesLinear Provider Catalog Integration
MCP Server UI Translations
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/openhuman/composio/providers/linear/provider.rs (1)
230-233: ⚡ Quick winAdd
connection_idto branch/error logs for correlation.Several logs in this method omit
connection_ideven though it is available in scope, which makes multi-connection debugging harder.As per coding guidelines, “Use
log/tracingatdebugortracelevel on RPC entry and exit, error paths, state transitions, and any branch that is hard to infer from tests alone… and include correlation fields such as request IDs, method names, and entity IDs when available.”Also applies to: 276-279, 292-292, 355-359, 365-368, 377-377
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/composio/providers/linear/provider.rs` around lines 230 - 233, The tracing calls in this method (e.g., the tracing::info call that logs "budget exhausted mid-sync, stopping pagination" and the other branch/error logs around the ranges noted) are missing the connection_id field; update each tracing invocation in this function to include connection_id = connection_id (or connection_id = %connection_id if you prefer display formatting) so all branch, error, and important transition logs (including the calls around the other ranges you flagged) contain the correlation id; locate these in the current function by the existing tracing::info/tracing::warn/tracing::error calls and add the connection_id field to their structured fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/openhuman/composio/providers/linear/provider.rs`:
- Around line 252-257: The transport call to ctx.execute(ACTION_LIST_ISSUES,
Some(args)) can early-return on error before persisting SyncState; modify the
map_err (or surrounding await) to persist the current state by calling
state.save(&memory) (or the appropriate save method) before returning the
formatted error (include page_num and the formatted error string as currently
done); ensure this change references ctx.execute, ACTION_LIST_ISSUES,
state.save, memory and page_num so the sync counters/markers are written to
storage on transport failures prior to propagating the error.
In `@src/openhuman/composio/providers/linear/sync.rs`:
- Around line 99-109: The viewer-id extractor extract_viewer_id currently falls
back to generic "id" and "data.id" keys which can return non-viewer ids; update
the pick_str key list in extract_viewer_id to only include explicit viewer/user
paths ("viewer.id", "data.viewer.id", "user.id", "data.user.id") and remove "id"
and "data.id" so the assignee filter is driven strictly by viewer/user fields.
---
Nitpick comments:
In `@src/openhuman/composio/providers/linear/provider.rs`:
- Around line 230-233: The tracing calls in this method (e.g., the tracing::info
call that logs "budget exhausted mid-sync, stopping pagination" and the other
branch/error logs around the ranges noted) are missing the connection_id field;
update each tracing invocation in this function to include connection_id =
connection_id (or connection_id = %connection_id if you prefer display
formatting) so all branch, error, and important transition logs (including the
calls around the other ranges you flagged) contain the correlation id; locate
these in the current function by the existing
tracing::info/tracing::warn/tracing::error calls and add the connection_id field
to their structured fields.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d2e13a1d-0fff-4bbf-9b4b-e7606f2d3317
📒 Files selected for processing (10)
src/openhuman/composio/providers/catalogs.rssrc/openhuman/composio/providers/catalogs_productivity.rssrc/openhuman/composio/providers/descriptions.rssrc/openhuman/composio/providers/linear/mod.rssrc/openhuman/composio/providers/linear/provider.rssrc/openhuman/composio/providers/linear/sync.rssrc/openhuman/composio/providers/linear/tests.rssrc/openhuman/composio/providers/linear/tools.rssrc/openhuman/composio/providers/mod.rssrc/openhuman/composio/providers/registry.rs
Three items from the CodeRabbit review: 1. [major] `ctx.execute(ACTION_LIST_ISSUES, ...)` in the pagination loop early-returns on transport error via `?` before `state.save(&memory)`. Request counters / synced markers accumulated earlier in the same sync pass would be dropped on a mid-loop flap, so the next sync would burn through the daily cap re-fetching pages we already drained. Replaced the `?` with an explicit match that persists state on the error path before propagating, mirroring the discipline the successful-response branch already follows. 2. [major] `extract_viewer_id` accepted generic `id` / `data.id` fallback paths. Since this value drives the assignee filter for the whole sync, picking up a non-viewer identifier (e.g. the first item id in a list response that Composio collapsed) would silently scope the sync to the wrong user and leak issues from another teammate. Removed the two permissive fallbacks; only explicit viewer/user paths are probed now. Docstring updated to call out the safety rationale. 3. [minor] Several `tracing::info!` / `tracing::debug!` / `tracing::warn!` calls inside `sync()` omitted `connection_id` even though it was in scope, which makes multi-connection debugging harder. Added `connection_id = %connection_id` to the six branch / error / transition logs CodeRabbit flagged (budget-exhausted-mid-sync, empty-page, missing-id, persist failure, cursor-boundary-stop, no-next-cursor). Plus a clippy doc-list overindent in `mod.rs` collapsed to single line. All 38 linear unit tests still pass; 303 composio::providers tests still pass — no regression.
graycyrus
left a comment
There was a problem hiding this comment.
Review — Linear Memory Provider
Solid, well-structured addition. This mirrors the ClickUp provider pattern faithfully and all five registration touchpoints are wired correctly. The sync model (viewer-id resolve → assignee-scoped pagination → cursor-based dedup → persist) is clean, and the privacy posture (assigned issues only) is appropriate.
Gates: CI pass ✓ | Conflicts pass ✓ | Coverage gate pass ✓
CodeRabbit overlap: Both findings (persist-before-return on transport failure, narrow viewer-id extraction) were addressed in 91942a9 — nothing to repeat.
Issue #2400 alignment: All acceptance criteria are met — provider registered, incremental sync with cursor, assignee filter, budget check, catalog migration, unit tests.
Cross-cutting check: LINEAR_CURATED re-export removed from catalogs.rs cleanly — catalog_for_toolkit("linear") now routes through the provider module. No other call sites reference the old re-export path (CI confirms).
One minor note:
[minor] sync.rs inline tests and tests.rs have ~15 overlapping test cases (identical assertions on the same helpers). The tests.rs module tests are the right home since they're the public integration-level tests for the module — the inline sync.rs tests could be trimmed to avoid doubling the maintenance surface. Not blocking.
Clean PR — no critical or major findings. Nice work following the established provider pattern end to end. 👍
Addresses graycyrus's tinyhumansai#2402 review minor note: "sync.rs inline tests and tests.rs have ~15 overlapping test cases (identical assertions on the same helpers). The tests.rs module tests are the right home since they're the public integration-level tests for the module — the inline sync.rs tests could be trimmed to avoid doubling the maintenance surface." Strategy: - Make tests.rs the canonical home for all helper tests. - Delete the entire `#[cfg(test)] mod tests {...}` block from sync.rs (was ~165 lines, 20 inline tests). - One inline test had unique coverage not duplicated in tests.rs: `extract_pagination_end_cursor_skips_empty_cursor_string` (validates that hasNextPage:true + whitespace endCursor returns None, preventing an infinite loop on the caller side). Moved that test into tests.rs verbatim (with an inline comment explaining the migration). - The other inline-only test (`now_ms_returns_nonzero`) tested a trivial wrapper around `SystemTime::now()` — removed without re-homing since the assertion `> 0` is structurally guaranteed by the underlying API. Result: - sync.rs loses 165 lines of test code and its `#[cfg(test)]` block entirely — module is now extract helpers only. - tests.rs gains 1 test (the cursor-edge case migration). - Net: 165 lines deleted, 16 lines added, single source of truth for Linear provider unit tests. Tests: 19 linear unit tests pass (was 38 with full duplication; -19 net = -20 removed + 1 migrated). All 284 `composio::providers` tests still pass — no regression. Verified locally: - `cargo check --lib` clean - `cargo test --lib composio::providers::linear` 19/19 pass - `cargo test --lib composio::providers` 284/284 pass - `cargo fmt --check` clean
|
@graycyrus thanks for the careful read. Minor note (sync.rs inline tests vs tests.rs duplication) — addressed in One inline test had coverage not duplicated in Net diff: -166 / +16 lines, no behaviour change. 19 linear unit tests pass (was 38 with duplication); all 284 Ready for re-review. |
# Conflicts: # src/openhuman/composio/providers/catalogs_productivity.rs # src/openhuman/composio/providers/descriptions.rs # src/openhuman/composio/providers/linear/mod.rs # src/openhuman/composio/providers/linear/provider.rs # src/openhuman/composio/providers/linear/sync.rs # src/openhuman/composio/providers/linear/tests.rs # src/openhuman/composio/providers/linear/tools.rs # src/openhuman/composio/providers/mod.rs
|
Resolved merge with main after #2452 shipped the equivalent Linear provider implementation. Merge resolution Main merged #2452 ("feat(composio): add Linear as a native memory provider") first, which added a parallel implementation of the same feature. The The remaining diff of this PR vs
Outdated review threads The two prior CodeRabbit inline comments on Validation
Push note Pushed with |
|
@senamakel — really appreciate the careful conflict resolution and preserving the four polish bits. The pointer-stubs in particular will save the next contributor some hunting. Thanks for taking the time. |
…merge Commit e9c9374 (tinyhumansai#2361) accidentally removed all settings.developerMenu.mcpServer.* and settings.mcpServer.* keys from de-5.ts, leaving German as the only locale without translations for the MCP server settings panel. Other locales (en, ar, bn, es, fr, hi, id, it, ko, pt, ru, zh-CN) still have them. Restored the 18 missing keys with the same German translations they had before tinyhumansai#2361.
Summary
LinearProviderundersrc/openhuman/composio/providers/linear/, joining the existinggmail/notion/slack/clickupproviders as the fifth toolkit with native Memory Tree ingest. Linear was previously exposed only as a catalog-only toolkit (LINEAR_CURATEDincatalogs_productivity.rs) — tool-calling worked, but the connected workspace's issues never reached long-term memory.composio/providers/clickup/as a structural template, with Linear-specific shape (Relay-style pagination viapageInfo.endCursor, ISO 8601updatedAt, GraphQLviewerfor user identity, workspace-prefixedidentifierlikeOH-123surfaced in the chunk title).Problem
composio/providers/today has working memory-ingest providers for gmail, notion, slack, and clickup. For dev-shaped users, the equivalent center of gravity is Linear — and there's nothing pulling issues / comments / project context into the Memory Tree on the periodic sync path. Composio already brokers the credentials and exposes the relevant read actions (LINEAR_LIST_LINEAR_ISSUES,LINEAR_GET_LINEAR_ISSUE, etc.), so this is a "plug Linear into the existing pattern" PR, not a new architecture.Solution
New module:
src/openhuman/composio/providers/linear/(5 files, ~1043 LOC):Five registration touchpoints (same shape ClickUp landed in #2291):
composio/providers/mod.rs:pub mod linear;composio/providers/registry.rs::init_default_providers: one extraregister_provider(...)line.composio/providers/mod.rs::has_native_provider: add"linear".composio/providers/mod.rs::native_provider_sync_interval: add"linear" => ....composio/providers/mod.rs::catalog_for_toolkit: repoint"linear"fromcatalogs::LINEAR_CURATEDtolinear::LINEAR_CURATED.Plus the natural housekeeping:
composio/providers/descriptions.rs: Linear description updated to mention Memory Tree sync (matches the ClickUp description shape).composio/providers/catalogs.rs+catalogs_productivity.rs: removed the now-supersededLINEAR_CURATEDdeclaration. Stub comment incatalogs_productivity.rspoints the next reader at the new home so the migration history is discoverable.Sync model (mirroring ClickUp)
SyncState::load("linear", connection_id)from the shared KV store.DEFAULT_DAILY_REQUEST_LIMIT = 500).LINEAR_GET_VIEWER— Linear'sLINEAR_LIST_LINEAR_ISSUESfilter{ assignee: { id: { eq: <viewer_id> } } }needs the connected user's id to scope to "my issues".LINEAR_LIST_LINEAR_ISSUESwithorderBy: updatedAt, sortDirection: descending, first: <page_size>, after: <cursor>. Stop each pass early when an issue'supdatedAtfalls at or below the saved cursor (and the compositeissue_id@updatedAtkey is already insynced_ids), or when Linear's Relay-stylepageInfo.hasNextPagereportsfalse.persist_single_item. Dedupe by compositeissue_id@updatedAtso edited issues re-ingest (same trick Notion uses forlast_edited_timeand ClickUp uses fordate_updated).updatedAtseen, recordlast_sync_at_ms, save state.Source-id convention
composio-linear-issue-<issue_uuid>— stable per issue across syncs so re-ingestion upserts rather than duplicates. Document title is"Linear <identifier>: <title>"(e.g."Linear OH-123: Wire up tag write tool") so the workspace-prefixed identifier surfaces in search hits — that's how humans refer to Linear issues in conversation.Curated tool catalog
LINEAR_CURATEDexposes 22 Linear actions split across the standard scopes:LINEAR_GET_VIEWER), issues (LIST_LINEAR_ISSUES,GET_LINEAR_ISSUE,SEARCH_ISSUES), workspace structure (teams / projects / states / cycles), users, labels.DELETE_LINEAR_ISSUE,REMOVE_ISSUE_LABEL).LINEAR_GET_VIEWERis the one slug not already in the pre-migrationLINEAR_CURATED— adding it because the sync path needs the authenticated user's id to build the assignee filter. The remaining 21 actions are migrated verbatim fromcatalogs_productivity::LINEAR_CURATED, just relocated next to the provider so the catalog and the impl live together (consistent with gmail / notion / clickup).Submission Checklist
provider_metadata_is_stable,curated_tools_contains_core_read_surface), capability matrix registration (capability_matrix_includes_linear_as_native_memory_provider), anddefault_impl_matches_new(observable equivalence — caught a no-op test pattern in the ClickUp PR's CodeRabbit review and applied the fix proactively).sync()async happy path (covered behind a Composio ProviderContext the existing test harness doesn't stand up — same as the Notion / Slack / ClickUp tests don't exercise the livesync()end-to-end either). Helper layer is unit-tested directly.Closes #2400in## Related.Impact
SyncStateKV namespaces, their registered tool catalogs, and all catalog-only toolkits are unchanged. TheLINEAR_CURATEDmigration is source-internal —catalog_for_toolkit("linear")still returns the same actions to the meta-tool layer.MAX_PAGES_PER_SYNC = 20,PAGE_SIZE = 50steady-state (100for the initial backfill), and the sharedDailyBudget(500 req/day) caps total API churn the same way it does for the other providers.assignee = viewer_id) prevents accidental ingest of other teammates' private issues. Composio handles credentials; no new secret-handling code.Transparency notes
LINEAR_GET_VIEWERslug name is modelled on Composio's standard<TOOLKIT>_<ACTION>naming convention; the remaining 21 are migrated verbatim from the existingLINEAR_CURATEDand were already in use by the meta-tool layer. IfLINEAR_GET_VIEWERdiffers from the live catalog name (e.g.LINEAR_GET_AUTHENTICATED_USER), it's a one-line string change with no architectural impact — happy to amend on review.LINEAR_LIST_LINEAR_ISSUES({ assignee: { id: { eq: ... } } }) mirrors Linear's GraphQL filter pattern. Composio's wrapping may flatten this to a simpler shape (e.g.{ assignee_id: ... }); if so, the change is also one line inprovider.rs::sync().Both are typical "first contact with a live Composio toolkit" risks and the same kind of issue that surfaced harmlessly on the ClickUp PR's first review pass.
Related
src/openhuman/composio/providers/clickup/(feat(composio): add ClickUp provider for Memory Tree ingest #2291, merged).src/openhuman/composio/providers/traits.rs.src/openhuman/composio/providers/sync_state.rs.AI Authored PR Metadata
Linear Issue
Commit & Branch
feat/linear-memory-providerupstream/mainat fetch timeValidation Run
pnpm --filter openhuman-app format:check— Rust-only change.pnpm typecheck— Rust-only change.cargo test --lib composio::providers::linear(38/38 pass — combines 18 intests.rsand 20 insync.rsinline tests);cargo test --lib composio::providers(303/303 pass — no regression on gmail / notion / slack / clickup or any catalog-only toolkit).cargo fmt --checkclean;cargo check --libclean (pre-existing warnings only);cargo clippy --lib --no-depsno new warnings incomposio/providers/linear/.app/src-tauri/src/**changes.Validation Blocked
Behavior Changes
ConnectionCreatedhook.Parity Contract
SyncStateKV namespaces (composio-sync-statekeyed by(toolkit, connection_id)) are unchanged.ComposioProvidertrait contract — daily budget, dedup-by-id, cursor-based pagination, idempotentpersist_single_itemupserts.LINEAR_CURATEDmigration fromcatalogs_productivitytolinear::toolspreserves the meta-tool layer's view of the curated catalog:catalog_for_toolkit("linear")returns the same&[CuratedTool]shape, just sourced from the provider's own module instead of the catalog-only file.Duplicate / Superseded PR Handling
LinearProviderimpl.Summary by CodeRabbit
New Features
Improvements