This PR:
- builds on top of https://github.com/ComposioHQ/composio/pull/3944
- reports `checkStatus: "unknown"` instead of fabricating an up-to-date
result when GitHub refreshes fail
- preserves the last successful release cache and throttles failed
attempts separately
- uses `Command.provideEffectDiscard` to start background checks after
parsing and skips explicit `composio version --check` invocations
- validates installed release versions before semver comparison
- adds deterministic unit concurrency coverage and compiled-binary CLI
E2E coverage
## Verification
- `pnpm --filter @composio/cli test` (`963` passed, `1` skipped)
- `pnpm typecheck` (`14/14` tasks)
- `pnpm build:packages` (`19/19` tasks)
- `pnpm --filter @e2e-tests/cli-version test:e2e:cli` (`9` passed)
- `pnpm build:binary` followed by `pnpm install:binary`
- installed Mach-O binary smoke checks for plain version, live and
cached `version --check`, unknown status, and both leading `--log-level`
forms
- scoped ESLint on all changed TypeScript files
- `git diff --check`
Repository-wide `pnpm lint` remains red on 52 existing errors across
unrelated docs and scripts; the changed TypeScript files pass scoped
ESLint.
## Summary
A user reported that a first-time CLI install "didn't get added to their
PATH". Investigation showed the PATH block *is* written correctly — but
the user never learns about it: `install.sh` delegates shell integration
to `composio install` with stderr captured to a temp file, and the CLI's
TerminalUI suppresses all decoration when stderr isn't a TTY. So the
"Updated ~/.zshrc" / "Restart your shell" guidance is silently
swallowed, the installer ends with "To get started, run: composio
--help", and the user gets `command not found` in their current shell.
This is the minimal fix: state explicitly in the installer's closing
message that composio was added to PATH and takes effect after
restarting the shell.
## Changes
- `install.sh`: closing message now reads "Composio was added to your
PATH — restart your shell (or open a new terminal) for it to take
effect." before the get-started commands. The block prints on every
install path (delegated and fallback).
## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change
## How Has This Been Tested?
- `bash -n install.sh`
- Stubbed end-to-end run of the post-download section (delegation
success and failure paths) in a sandboxed `$HOME`, verifying the message
prints in both.
- Reproduced the original issue beforehand by running the real installer
(released `@composio/cli@0.2.32`) into a fake `$HOME`: PATH block
written, zero user-facing messaging about it.
## Screenshots (if applicable)
```
Composio was added to your PATH — restart your shell (or open a new terminal) for it to take effect.
Then, to get started, run:
composio --help
composio login
```
## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [x] I updated documentation as needed
- [x] I added tests or explain why not applicable — shell-script-only
messaging change; no test harness exists for install.sh output
- [x] I added a changeset if this change affects published packages —
not applicable, install.sh is not a published package and
`@composio/cli` is Changesets-ignored
## Additional context
install.sh is served from `next`, so this reaches all new installs
immediately on merge and works with the already-released binary — no CLI
release required.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Part of
[PRDE-1143](https://linear.app/composio/issue/PRDE-1143/prompt-users-to-upgrade-composio-cli-when-new-versions-are-available).
## What
`composio version --check` prints one JSON object to stdout and exits 0:
```json
{"current":"0.2.31","latestStable":"0.2.32","updateAvailable":true,"lastChecked":"2026-07-24T19:15:15.413Z"}
```
The startup upgrade banner is TTY-gated, so scripts and agent-driven
(piped) CLI usage never learn a newer stable exists. `--check` gives
them an explicit query: if `updateAvailable`, run `composio upgrade`.
It reuses the update notice's self-throttled release cache
(`~/.composio/update-check.json`, unchanged shape — PRDE-1144 reads it):
fresh <24h means no network call; stale means a synchronous GitHub fetch
with a 10s timeout whose failures degrade to the cached/absent state
instead of erroring. Purely additive — the existing banner behavior is
untouched.
## Verification
Before writing anything, the existing notice pipeline was verified
empirically with a real stale install (stable `0.2.31` binary, isolated
`$HOME`, real pty): background fetch completes even on fast commands,
the banner fires on the next TTY run, piped output stays silent, and
symlinked installs resolve `release-tag.txt` correctly.
- 4 unit tests for `getUpdateStatus`; full CLI suite 955 passed,
typecheck clean.
- Exercised live from source: pure JSON on stdout, empty stderr, exit 0.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Automated sync of backend data into the docs site. Triggered by:
`workflow_dispatch`.
## What changed
- **Toolkit catalog** (`docs/public/data/toolkits.json`,
`toolkits-list.json`) — refreshed list of available toolkits, auth
schemes, and tools from the backend API
- **OpenAPI specs** (`docs/public/openapi.json`,
`docs/public/openapi-v3.json`) — latest v3.1 and v3.0 API specifications
fetched from production
- **API reference pages** (`docs/content/reference/api-reference/`,
`docs/content/reference/v3/api-reference/`) — regenerated index pages
for both API versions
- **Meta tools reference** (`docs/public/data/meta-tools.json`,
`docs/content/toolkits/meta-tools/*.mdx`) — updated meta tool schemas
and reference docs
Co-authored-by: sudodaksh <23355449+sudodaksh@users.noreply.github.com>
## Why
`docs.composio.dev/toolkits` has been serving a stale catalog. Toolkits
added to
production since 2026-07-13 — `saperly`, among others — have no page and
404,
while being live in the API and in `composio search`.
The page is statically generated from the committed
`docs/public/data/toolkits.json`,
which this workflow is supposed to refresh every 5 hours. It has failed
on **every
run since 2026-07-13** (60 of the last 100 runs), starting with the run
immediately
after #3770.
## Root cause
#3770 correctly moved this job onto production and added a guard
rejecting any
non-production base URL. But `secrets.COMPOSIO_API_KEY` is shared with
`ts.test-e2e`, `py.test`, `py.check` and `ts.examples-nightly` — all of
which pin
`COMPOSIO_BASE_URL` to staging. It is a staging credential, and it
returns 401
against `backend.composio.dev`.
Evidence it was live, not expired: `ts.examples-nightly` has been green
against
staging using that same key.
Repointing the shared secret at production is not an option — it would
break the
staging suites. So this job takes its own key.
## Changes
1. **`COMPOSIO_DOCS_API_KEY`** — the docs job reads a dedicated
production secret.
The env var name stays `COMPOSIO_API_KEY` because that is what the
generators read.
2. **Failure alerting** — an `if: failure()` step files one tracking
issue, deduped
by label so a 5-hourly cron does not open 60 duplicates.
## Why alerting matters here
This workflow fails silently by construction. When it breaks, the
catalog simply
stops being refreshed, the docs site keeps building from the last good
commit, and
nothing 500s. There is no user-visible signal short of someone noticing
a missing
toolkit page. That is how this went unnoticed for 11 days.
## Required before merge
`COMPOSIO_DOCS_API_KEY` must be set with **production** read access and
these
permission areas: Toolkits (read), Tools (read), Triggers (read), and
**Sessions (read and write)** — `generate-meta-tools.ts` creates a
tool-router
session and then reads its tool list, so write-only is not sufficient.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The job sets `defaults.run.working-directory: ./docs`, which the alerting step
inherited. That path only exists once checkout has run, so any failure before it
— the app-token step, for instance — would leave the runner unable to start the
step's shell. Combined with continue-on-error, the alert would be dropped
silently for exactly the earliest and least visible failures.
Pin the step to ${{ github.workspace }}, which the runner creates before any
step executes.
Reported by Cursor Bugbot on #3947.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`secrets.COMPOSIO_API_KEY` is shared with ts.test-e2e, py.test, py.check and
ts.examples-nightly. Those pin COMPOSIO_BASE_URL to staging, and the nightly
examples run is currently green against staging with this key — so the secret is
a live staging credential, not an expired one.
#3770 moved this job to production and added a guard rejecting any non-production
base URL, but no production credential was provisioned. The staging key has
returned 401 against backend.composio.dev on every run since, silently freezing
the committed toolkit catalog.
Repointing the shared secret at production would fix docs and break the staging
suites, and py.check sets no base URL at all — a production key there would aim
type-inference tests at production. So take a separate secret instead.
Requires COMPOSIO_DOCS_API_KEY to be set with production read access.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The scheduled `Docs - Update Data` workflow has failed on every run since
2026-07-13 and nobody noticed for 11 days. The failure mode is silent by
construction: when the sync breaks, `docs/public/data/toolkits.json` simply
stops being refreshed, the docs site keeps building from the last good commit,
and nothing 500s. Toolkits added to production after the last green run just
have no page and 404 — which is how `saperly` ended up missing from
docs.composio.dev/toolkits while being live in the API.
Add a `failure()` step that opens one tracking issue, deduped by label so a
5-hourly cron does not file 60 duplicates. The body names the most likely cause
up front: `COMPOSIO_API_KEY` needs production read access against
backend.composio.dev, and a staging-only key returns 401.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The startup upgrade banner is TTY-gated, so scripts and agent-driven (piped)
CLI usage never learn a newer stable exists. `composio version --check`
prints a JSON status to stdout — {current, latestStable, updateAvailable,
lastChecked} — reusing the update notice's self-throttled release cache
(fresh <24h: no network; stale: synchronous fetch, 10s timeout, failures
degrade to the cached/absent state).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Strip the review-round comments per feedback; rationale for the non-obvious
ones lives in the fix commits (c22c7b32 post-alias fallback, b9740b8c credential
ordering).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Revert the gratuitous restructuring of the three binary build scripts. The
earlier version pulled `'bun'` out of the args array and dropped
`as const satisfies ReadonlyArray<string>` to work around a typecheck error —
but a variadic tuple (`['bun', ...posthogBakeArgs(), ...] as const`) keeps its
first element known, so `Command.make(...args)` still typechecks. Each script's
diff is now just the `posthogBakeArgs` import plus the one inserted spread.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CLI's PATH-setup output is swallowed by the installer's stderr
capture, so first-time users were never told the PATH change only takes
effect in a new shell — they ran `composio --help` in the same terminal,
got "command not found", and concluded the install had failed to set up
PATH. State it explicitly in the closing message.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Going direct to PostHog dropped the org/project context Apollo's Segment path
enriched server-side. org_id is available in ~/.composio/user_data.json — which
the analytics worker already reads — so post-login events now carry it. project_id
is per-session and not reliably in user config, so it is left for a follow-up
rather than fabricated; the PR description is corrected accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Once $create_alias fires, PostHog permanently merges install_id into the Apollo
person. Falling back to that same install_id on logout or on a credential
fingerprint mismatch therefore did NOT anonymise anything — person-level
insights still resolved those events to the previous user. The guard added in
b9740b8c changed the mechanism of the cross-user attribution bug rather than
removing it.
getDistinctId now returns:
- the bare install_id only while the install has never been aliased, so the
login alias still merges the pre-login funnel correctly;
- user_<credential fingerprint> when a credential is active but does not match
the stored identity — pseudonymous per credential, never merged;
- anon_<install_id> when signed out after an alias.
Verified live against a throwaway PostHog project: the bare install_id and the
Apollo id both resolve to person 42ff3120, while post-logout events under
anon_<install_id> resolve to a different person entirely. The earlier trace had
shown both ids collapsing to one person; I read that as the merge working and
missed that it also meant the logout fallback attributed to the logged-out user.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two bugbot findings on the previous fix.
- The credential fingerprint was read back via getUserApiKey, but every login
path resolves identity BEFORE persisting the new credential (link at 327/512/720
vs ctx.login at 342/560/756). So the stored fingerprint described the previous
key — or none on a fresh install — and could never match, leaving every
post-login event anonymous. That defeated the identity stitch entirely. The
credential is now passed in explicitly by the caller (each already holds the
key it just validated), so identity no longer depends on write ordering.
- spawnWorker swallowed DetachedProcessSpawnError and enqueue returned true
regardless, so a failed spawn still marked the alias as sent and it was never
retried — defeating the field added for exactly that purpose. Spawn failure
now propagates through enqueue.
Added a regression test that reproduces the login ordering (no readable
credential at link time); verified it fails without the fix, producing the
install_id instead of the Apollo id.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-ups.
- install.sh ran `login --agent` without an origin marker while its `install`
and `setup` invocations both set one, so installer-driven agent logins were
indistinguishable from manual ones — the exact ambiguity this PR removes for
the other two.
- Hoist the release channel to a module constant; both inputs are constants, so
recomputing it per event was pointless.
- The changelog overstated the gap: a Homebrew user running `composio install`
by hand still emits install events, they just carry no installer origin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-ups on the direct-to-PostHog telemetry.
- Cross-user attribution (the serious one). getDistinctId trusted the persisted
apollo_user_id regardless of which credential was actually in use, while
getUserApiKey gives COMPOSIO_USER_API_KEY precedence over the stored config.
A script running under user B's env key therefore emitted events attributed
to whoever last logged in — and a backfilling command under B's key would
overwrite the stored id, flipping later events the other way. The pre-rewrite
code derived identity from the key itself and did not have this problem.
Now a fingerprint of the resolving credential is persisted alongside the id,
and the id is only used while that fingerprint still matches; otherwise we
fall back to the anonymous install_id. Unsure attribution is anonymous rather
than wrong.
- clearApolloIdentityForAnalytics ignored the opt-out and called
getOrCreateInstallId, so `composio logout` with telemetry disabled created
~/.composio/analytics.json and minted an install id — contradicting the
invariant asserted elsewhere in these tests. It now reads state and does
nothing when there is nothing to clear.
- An install is aliased at most once, ever. Logging in as a second user on the
same device no longer emits a second $create_alias for an install PostHog has
already identified.
- Dropped the now-unused readApolloUserId.
Tests: stale-identity fallback under a differing env key, logout-while-disabled
creating no file, and no re-alias across logout + login as a different user.
The post-login test now establishes identity through the public API rather than
hand-writing state, so it exercises the credential binding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every CLI event now carries journey_stage (install|setup|login|connect|
execute|other) via an exhaustive event->stage map in events.ts, and
cli_channel (stable|beta) derived from the existing
inferSkillReleaseChannel(APP_VERSION). Both are applied by a single
buildEvent factory so no builder hand-writes them.
install.sh now sets COMPOSIO_CLI_INVOCATION_ORIGIN=installer on its
"$exe" install run (matching the setup run), so script installs are
distinguishable from a user manually running `composio install`.
npm and Homebrew installs never run install.sh, so the install stage
only covers install.sh installs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review feedback (KJ):
- Strip the added explanatory comments across dispatch.ts, constants.ts,
_shared.ts, build-binary.ts and the workflow; keep only pre-existing ones.
- Shorten the CHANGELOG entry to a single line.
- Revert the .gitignore addition.
Bugbot findings:
- Already-authenticated installs never stitched (events stayed anonymous after
upgrade, since only `login` persisted the id). Backfill now runs from any
command that already resolves a session: whoami, init, connected-accounts
link, plus the existing login paths.
- `composio login --agent` skipped the stitch; the agent branch now resolves
session info best-effort and links.
- `composio logout` left `apollo_user_id` behind, so later events kept
attributing to the previous person. Added clearApolloIdentityForAnalytics.
- A failed alias was never retried, because any persisted `apollo_user_id`
counted as "already aliased" — including when nothing was ever enqueued (no
project key / telemetry off). Track `aliased_apollo_user_id` separately and
only record it once the enqueue actually happened.
Tests: alias retry after a skipped enqueue, and logout reverting to install_id.
Full suite 956 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-ups on the direct-to-PostHog telemetry:
- linkApolloIdentityForAnalytics now checks the disabled/opt-out state BEFORE
touching disk, so a disabled (TELEMETRY_DISABLED/CI/etc.) login no longer
creates ~/.composio/analytics.json with the install_id + Apollo id. Honors
the disabled-means-no-op contract.
- Emit the $create_alias only on the first association of an install with an
Apollo user: a repeat login is redundant, and PostHog rejects re-aliasing one
install into a second account. Gates on the persisted apollo_user_id.
Tests: disabled path now asserts the identity file is absent; alias test
asserts a repeat login does not re-spawn the alias worker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Short-circuit enqueuePostHogEnvelope when no PostHog project key is
configured, so we never spawn a detached worker that would only no-op.
The pre-rewrite code guarded on the endpoint before spawning; the direct
transport lost it, so every command on a keyless build (the default until
the key is baked, and all OSS forks / local builds) spawned a throwaway
process. Added a parent-side test asserting no spawn without a key.
- Remove three duplicated inline comments at the login call sites (the
function name + its definition comment already document it).
- Ignore .claude/plans/ (handoff artifacts, never committed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
`composio install` uses a temporary file and rename so shell config
updates are atomic. When the config path is a symlink, that rename
replaces the symlink itself. This breaks setups that use tools such as
GNU Stow to manage and sync dotfiles across machines.
Fixes#3922
## Changes
- Resolve valid symlink chains before choosing the temporary file and
rename target.
- Keep regular shell config files on the existing write path.
- Add a regression test using a relative `.zshrc` symlink.
## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change
## How Has This Been Tested?
- `mise exec -- pnpm --filter @composio/cli exec vitest run
test/src/commands/install.cmd.test.ts` — 13 passed
- `mise exec -- pnpm --filter @composio/cli typecheck`
- ESLint and Prettier on the two changed files
## Screenshots (if applicable)
Not applicable.
## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [x] I updated documentation as needed
- [x] I added tests or explain why not applicable
- [x] I added a changeset if this change affects published packages
## Additional context
No changeset is needed because `@composio/cli` is ignored by the
repository's Changesets configuration.
The current `next` branch is missing the `#ssrf_guard` alias in the CLI
Vitest config. I added that alias locally to run the focused test, then
removed it before committing this patch.
---------
Co-authored-by: Alberto Schiabel <jkomyno@users.noreply.github.com>
Co-authored-by: jkomyno <alberto@composio.dev>
This PR:
- follows up on https://github.com/ComposioHQ/composio/pull/3838
- renders the existing `Legacy` badge in v3 and v3.1 endpoint headers
when the OpenAPI operation has `deprecated: true`
- centralizes deprecated-endpoint badge copy across API overview tables
and detail pages
- adds static coverage plus running-server assertions for the reported
routes and active control endpoints
- verifies the fix with `bun run test`, `bun run types:check`, `bun run
lint:links`, `bun run build`, and the focused integration suite
## What
Every link to `dashboard.composio.dev` in the docs now carries
`utm_source=docs`, `utm_medium`, and `utm_campaign`, so sign-ups driven
by docs show up attributable in PostHog instead of relying on (lossy)
referrer data. Previously only the navbar Playground button was tagged.
On top of that, dashboard links are now held to a full link policy:
1. **UTMs required** on every dashboard link.
2. **No `app.composio.dev` or `platform.composio.dev`** anywhere (those
hosts don't exist).
3. **Go-link paths only**: any dashboard link with a path must be
`/~/project/...` or `/~/org/...` (so it resolves inside the user's
workspace), with `/login` as the one allowed exception.
## Link changes
- **MDX content** (30+ files): `utm_medium=content`,
`utm_campaign=<page-slug>` (e.g. `docs-quickstart`), so we can see
*which page* drives sign-ups. Code fences, inline code, and generated
`content/reference/**` are untouched.
- **Components**: toolkits landing Playground CTA
(`utm_medium=toolkits`) and the LLM guardrail markdown
(`utm_medium=llm-guardrails`). Deleted `landing-hero/_stub-links.ts` —
unused everywhere.
- **Go-link rewrites**: quickstart's 3× `/settings` →
`/~/project/settings/api-keys`; changelog's
`/{workspace}/{project}/settings` → `/~/project/settings/general`. (The
two `/settings` links in generated `content/reference/errors.mdx` need
the same fix upstream in the OpenAPI source.)
## Enforcement
- **ESLint** (`docs/eslint.config.mjs`): `no-restricted-syntax` errors
for all three rules on string/template literals in TS/TSX.
- **MDX** (ESLint can't parse it):
`tests/static/dashboard-links.test.ts` (3 tests), running in the
existing `docs-tests` CI workflow via `bun run test`. The banned-host
check scans all authored content including code fences; the UTM and
go-link checks apply to markdown links outside fences.
- Every rule was negative-tested (probe violations flagged, valid
go-links//login pass).
- Convention documented in `docs/AGENTS.md`.
## Zero ESLint errors
`bun run lint` previously exited with 25 pre-existing errors unrelated
to this change. Fixed the two `no-html-link-for-pages` ones properly
(`<Link>` in the toolkits page; intentional full-reload `<a>` in
`global-error.tsx` gets a commented disable). The 23 react-hooks
compiler violations (`set-state-in-effect`, `immutability`, `purity`)
across 15 interactive components are downgraded to `warn` with a comment
— refactoring animation-heavy components here would be risky and out of
scope; tracked to burn down separately and flip back to `error`.
## Verification
- `bun run lint` → **0 errors** (warnings all pre-existing)
- `bun test tests/static/` → 36 pass / 0 fail
- `bun run types:check` → clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Linear:
[UXE-188](https://linear.app/composio/issue/UXE-188/docs-utm-attribution-dashboard-link-policy-go-links-banned-hosts)
Hygiene pass on the direct-to-PostHog telemetry: condense verbose block
comments in dispatch.ts/constants.ts.
Source the public write-only PostHog project key from a build-time
COMPOSIO_POSTHOG_KEY env var, baked into the binary via `bun build --define`
(scripts/_shared.ts posthogBakeArgs), instead of a committed source literal.
This mirrors the dashboard's env-driven NEXT_PUBLIC_POSTHOG_TOKEN pattern and,
for this OSS repo, keeps the key out of public git history and out of fork/local
builds (they compile a no-op binary). `--define` is used rather than a second
`--env` because Bun honors only one `--env` and it would clobber the existing
DEBUG_OVERRIDE_* inline. CI passes the key as an Actions variable on the
cross-compile step; empty -> telemetry no-ops.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CLI analytics path now delivers events straight to PostHog's public
capture API instead of the Apollo /api/v3/cli/analytics endpoint. This removes
the base-URL gate that silently dropped every pre-login event, so install /
setup / plugin-setup telemetry now reaches PostHog keyed by the anonymous
install_id.
At login the CLI persists the Apollo org_member.id into ~/.composio/
analytics.json and fires a single $create_alias (install_id -> org_member.id),
so post-login events and all later CLI activity resolve to the same PostHog
person as web onboarding. distinct_id is now the persisted apollo_user_id when
present, else the bare install_id (no more anon_/user_ prefixing).
Delivery choice: direct-only (the Apollo send is dropped for CLI events). The
detached best-effort worker and all opt-outs are unchanged; no secrets or
tool-arg values are sent.
The public phc_* project-196278 write key is left empty with a TODO — direct
delivery is a safe no-op until the maintainer fills it in. Env overrides:
COMPOSIO_POSTHOG_KEY / COMPOSIO_POSTHOG_INGEST_URL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the dashboard link policy: no app.composio.dev or
platform.composio.dev anywhere, and any dashboard.composio.dev link with
a path must be a go-link (/~/project/... or /~/org/...) or /login.
Enforced via new no-restricted-syntax selectors for TS/TSX and the
static test (renamed to dashboard-links.test.ts) for MDX.
Rewrite the non-conforming links: quickstart /settings -> go-link for
api-keys (also restores UTM params lost to a stray checkout during
verification), changelog {workspace}/{project}/settings -> go-link for
project settings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tag every dashboard.composio.dev link in authored docs content and
components with utm_source=docs plus utm_medium/utm_campaign, and enforce
the convention going forward: an ESLint no-restricted-syntax rule covers
TS/TSX and a static bun test covers MDX (runs in docs-tests CI).
- content links use utm_medium=content and utm_campaign=<page-slug>
- content/reference is excluded (generated upstream)
- delete unused landing-hero/_stub-links.ts
- fix the no-html-link-for-pages errors; downgrade the pre-existing
react-hooks compiler violations to warnings until burned down, so
bun run lint exits with zero errors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What
Instruments the CLI's plugin auto-install (`composio setup`) and shell
integration (`composio install`) so the install → setup → activation
funnel is observable in PostHog. Re-lands the previously-deferred setup
lifecycle telemetry (`4e68e652`, reverted by `333d2296`) and extends it
with runtime detection/failure/abandonment signal.
### Part 1 — re-land deferred lifecycle telemetry
- New events: `CLI_INSTALL_*`, `CLI_SETUP_*` lifecycle families,
per-host `CLI_PLUGIN_SETUP_SUCCEEDED` /
`CLI_PLUGIN_UNINSTALL_SUCCEEDED`.
- Adds `'setup'` to `KNOWN_COMMAND_TOKENS` — fixes the current
misattribution where every `composio setup` logged as generic
`CLI_COMMAND_*` with `command_path='composio'`.
- `install.sh` tags the auto-setup call with
`COMPOSIO_CLI_INVOCATION_ORIGIN=installer` so installer-driven setups
are attributable.
### Part 2 — runtime extensions (fired from `setup.ts` / `setup.cmd.ts`,
which the argv-only central dispatch can't see)
- **`CLI_SETUP_HOST_DETECTED`** (per host) — `agent_host`, `available`,
`supported`, `host_version`, `unsupported_reason_code`. Sizes the plugin
channel (Claude vs Codex vs neither).
- **Normalized `unsupported_reason_code`** threaded through
`SetupTargetDetection` (`codex_too_old` / `no_json_inspection` /
`host_command_failed` / `unknown`) instead of string-matching messages.
- **`CLI_PLUGIN_SETUP_FAILED`** (per host) — previously only success was
emitted; failures were lost into the argv-only `CLI_SETUP_FAILED` with
no host/phase.
- **`CLI_SETUP_CANCELLED`** (declined confirm) / **`CLI_SETUP_SKIPPED`**
(`--if-present` with no host) — previously indistinguishable from
success.
- `stdout_is_tty` added to `CLI_SETUP_INVOKED`.
## Why
`composio setup` (plugin auto-install for Claude Code / Codex) and the
curl installer were firing **zero** dedicated telemetry, and `setup` was
misattributed on top of that — the entire plugin-driven acquisition path
was dark. These events make the install → setup → per-host plugin
install → first `composio execute` funnel joinable by `install_id` /
`distinct_id`.
## Layering note
The central `CLI_SETUP_SUCCEEDED`/`FAILED` mean "the command ran /
threw"; the in-command events describe **what actually happened**. A
user-cancelled run emits both `CLI_SETUP_CANCELLED` and
`CLI_SETUP_SUCCEEDED` by design — analyze outcomes off the specific
events (mirrors how `CLI_EXECUTE_SUCCEEDED` coexists with
`CLI_TOOL_INVOCATION_FAILED`).
## Verification
- Typecheck (`tsgo`, src + test): clean.
- Full CLI vitest suite: **85 files, 796 passed, 1 skipped** — includes
new cases in `analytics.events.test.ts` and a new
`setup.telemetry.cmd.test.ts` (stubbed `CommandRunner` covering
detection, install, per-host failure, cancel/skip).
- `test/install-sh-release-resolution.test.sh`: passes (installer-origin
assertion re-added).
- Confirmed the Apollo `/api/v3/cli/analytics` ingest has no event-name
allowlist (validates `event` as a ≤200-char string, forwards arbitrary
names) and traced the live pipe (`enqueue → delivery_succeeded`), so the
new event names flow through unchanged once shipped.
No backend changes required.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/ComposioHQ/codesmith/composio/pr/3902"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with Codesmith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787252592&installation_model_id=428187&pr_number=3902&repository=ComposioHQ%2Fcomposio&return_to=https%3A%2F%2Fgithub.com%2FComposioHQ%2Fcomposio%2Fpull%2F3902&signature=bef1dc2a149a6b593ff6bb391c1e7a204240cc7686ffa5e061ae889589fed72a"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with Codesmith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>/codesmith</code> with what you
need. Autofix is disabled.</sup>
<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->
Re-lands 4e68e652 (reverted by 333d2296) and extends it:
Part 1 (re-land):
- Dedicated CLI_INSTALL_* / CLI_SETUP_* lifecycle families; 'setup' added
to KNOWN_COMMAND_TOKENS so it is no longer misattributed to
command_path='composio'
- Per-host CLI_PLUGIN_SETUP_SUCCEEDED / CLI_PLUGIN_UNINSTALL_SUCCEEDED
fired from installSetupTargets/uninstallSetupTargets
- install.sh auto-setup runs with COMPOSIO_CLI_INVOCATION_ORIGIN=installer
Part 2 (extensions, fired from inside setup.cmd.ts / setup.ts):
- CLI_SETUP_HOST_DETECTED per host after detection, with agent_host,
available, supported, host_version and normalized unsupported_reason_code
(codex_too_old | no_json_inspection | host_command_failed | unknown)
threaded through SetupTargetDetection
- CLI_PLUGIN_SETUP_FAILED per host (phase install|uninstall) tapped before
the existing mapError rethrow
- CLI_SETUP_CANCELLED (user declined confirm) and CLI_SETUP_SKIPPED
(--if-present with no supported host)
- stdout_is_tty added to the setup command properties
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
- promote CLI multi-account selection out of the experimental feature
system so stale `multi_account: false` config can no longer disable
`execute --account`, `listen --account`, or `link --alias`
- add `proxy --account <alias|word_id|connected-account-id>` and pin the
resolved connected account into the Tool Router session
- expose account selection consistently in command help and the
generated `composio-cli` skill
- make duplicate-alias link errors explain how to select and inspect the
existing account
## Root cause
Multi-account selection was introduced behind `multi_account` in #3129.
#3163 enabled it by default for stable builds, but the command
implementations still consulted the experimental setting and the custom
help surface still omitted the selector. That left stale or explicit
`multi_account: false` config able to disable selection even though the
feature was intended to be stable. `proxy` never received equivalent
account-selection wiring.
This is an incomplete graduation of the feature rather than a recent
code regression. Existing implicit/default account selection behavior
remains unchanged; this PR does **not** require explicit selection.
Closes#3894.
## Tests
- `vitest run` in `ts/packages/cli`: **84 files passed, 785 tests
passed, 1 skipped**
- focused proxy/link/help regression suite: **26 tests passed**
- CLI source and test typechecks via `tsgo --noEmit`: passed
- `tsx ./scripts/validate-skills.ts`: passed for stable and beta builds
- Prettier and `git diff --check`: passed
## Notes
- No changeset: `@composio/cli` is excluded from Changesets; the CLI
changelog is updated directly.
- No VHS recording: this is a narrow selector-wiring/help regression
covered by command-level tests and requires a real multi-account fixture
to demo safely.
- The root Turbo typecheck entrypoint could not run in this worktree
because the local environment does not have `mise` on `PATH`; the
directly equivalent CLI source/test `tsgo` checks passed.
- GitHub CI: queued/pending at PR creation; no background CI watch
started per workspace policy.
This PR:
- activates the final rule group: `TryStatement` and `process.env` are
now ESLint-banned in CLI source, completing the enforcement ramp started
in the first PR of this stack; the env guard covers dot access, bracket
access, and object destructuring
- migrates the residual files (auth-config create, trigger create,
toolkit search, runtime debug flags, master detection, import-time UI
setup) off try/catch and ad-hoc env reads
- every surviving violation sits at a declared runtime boundary (bin.ts
bootstrap, child-process companion runtime, import-time UI setup,
environment writes and whole-environment enumeration, spawn-time env
handshakes) with an inline `// eslint-disable-next-line <rule> --
<reason>` justification
- adds `eslint-boundaries.json` — the checked-in manifest of the 46
sanctioned disables across 22 files — and `validate:boundaries` (part of
`pnpm test`), which scans both `.ts` and `.tsx` sources and fails on
undeclared, missing, relocated, bare, or file-wide disables
- pins each registered boundary by rule, justification, and governed
next-line target, with regression coverage for TSX discovery, same-file
retargeting, and duplicate semantic sites
- documents the Effect Boundary Policy in the CLI `AGENTS.md` and the
`cli-command` skill reference, and drops the gitleaks test-file
allowlist
---
**Stack** (re-slice of https://github.com/ComposioHQ/composio/pull/3859;
each PR targets its parent and auto-retargets to `next` as parents
merge):
1. https://github.com/ComposioHQ/composio/pull/3877 — guidelines + inert
rule groups
2. https://github.com/ComposioHQ/composio/pull/3878 — `new Function()`
eval security fix
3. https://github.com/ComposioHQ/composio/pull/3879 — behavioral fix
pack
4. https://github.com/ComposioHQ/composio/pull/3880 — platform-imports
rule + migration
5. https://github.com/ComposioHQ/composio/pull/3881 — terminal-streams
rule + TerminalUI boundary
6. https://github.com/ComposioHQ/composio/pull/3882 — descriptor seam +
zod→Schema
7. https://github.com/ComposioHQ/composio/pull/3883 — test-tree lint +
deterministic suites
8. https://github.com/ComposioHQ/composio/pull/3884 — typed error
boundaries + v4 seams
9. https://github.com/ComposioHQ/composio/pull/3885 — try/catch+env ban
+ boundary ratchet ← **this PR**
On stacked bases CI runs lint/build and the CLI Docker e2e job; unit
tests and typecheck are verified locally per PR and re-verified by full
CI when each PR is retargeted to `next`.
This PR:
- converts every remaining thunk-form `Effect.tryPromise` in CLI source
to a purpose-named `Data.TaggedError` preserving its cause —
`UnknownException` no longer surfaces from `src/` — and switches
best-effort recoveries from `catchAll` to `catchTag`
- isolates the `Symbol.for('effect/SpanAnnotation')` internal read
behind a shape-validating adapter, with a canary test that fails a real
effect through the runtime so an `effect` upgrade that drops the
mechanism breaks CI instead of silently losing span timelines
- decodes trigger-log payloads once at the API boundary through a
tolerant schema documenting camelCase/snake_case/meta-nested alias
precedence
- deduplicates the per-entry tolerant decoders into
`utils/collect-decoded-entries.ts`, replaces locally re-declared record
schemas with `JsonRecordSchema`, deletes the unused `EnvLangDetector`
service, and removes the temporary sync bridge from the platform-imports
slice
- the largest PR of the stack by design: generic refactors land late so
the fix and rule slices before it stay small; one commit per sub-theme
keeps `git show` reviewable
---
**Stack** (re-slice of https://github.com/ComposioHQ/composio/pull/3859;
each PR targets its parent and auto-retargets to `next` as parents
merge):
1. https://github.com/ComposioHQ/composio/pull/3877 — guidelines + inert
rule groups
2. https://github.com/ComposioHQ/composio/pull/3878 — `new Function()`
eval security fix
3. https://github.com/ComposioHQ/composio/pull/3879 — behavioral fix
pack
4. https://github.com/ComposioHQ/composio/pull/3880 — platform-imports
rule + migration
5. https://github.com/ComposioHQ/composio/pull/3881 — terminal-streams
rule + TerminalUI boundary
6. https://github.com/ComposioHQ/composio/pull/3882 — descriptor seam +
zod→Schema
7. https://github.com/ComposioHQ/composio/pull/3883 — test-tree lint +
deterministic suites
8. https://github.com/ComposioHQ/composio/pull/3884 — typed error
boundaries + v4 seams ← **this PR**
9. https://github.com/ComposioHQ/composio/pull/3885 — try/catch+env ban
+ boundary ratchet
On stacked bases CI runs lint/build and the CLI Docker e2e job; unit
tests and typecheck are verified locally per PR and re-verified by full
CI when each PR is retargeted to `next`.
This PR:
- un-ignores `ts/packages/cli/test` in the root ESLint config (fixtures
and the `test/__utils__` harness glue stay unlinted) and activates the
test rule group: no `Effect.run*` in suites (use
`it.effect`/`it.scoped`/`it.live`) and no `Date.now()`
- adds `test/__utils__/vitest.setup.ts`, pointing HOME and
`COMPOSIO_CACHE_DIR` at per-run temp directories so suites never touch
the developer's real `~/.composio` state
- migrates the remaining suites onto @effect/vitest runners with
layer-provided platforms, pins wall-clock reads via
`vi.setSystemTime`/TestClock-relative fixtures, and uses
`crypto.randomUUID()` for unique temp names
- lands the v3 Cause characterization suite pinning `reduceWithContext`
traversal over the six-constructor tree and rendered error output — the
executable equivalence oracle for the effect v4 migration
- tests arriving in later stack PRs must comply from birth
---
**Stack** (re-slice of https://github.com/ComposioHQ/composio/pull/3859;
each PR targets its parent and auto-retargets to `next` as parents
merge):
1. https://github.com/ComposioHQ/composio/pull/3877 — guidelines + inert
rule groups
2. https://github.com/ComposioHQ/composio/pull/3878 — `new Function()`
eval security fix
3. https://github.com/ComposioHQ/composio/pull/3879 — behavioral fix
pack
4. https://github.com/ComposioHQ/composio/pull/3880 — platform-imports
rule + migration
5. https://github.com/ComposioHQ/composio/pull/3881 — terminal-streams
rule + TerminalUI boundary
6. https://github.com/ComposioHQ/composio/pull/3882 — descriptor seam +
zod→Schema
7. https://github.com/ComposioHQ/composio/pull/3883 — test-tree lint +
deterministic suites ← **this PR**
8. https://github.com/ComposioHQ/composio/pull/3884 — typed error
boundaries + v4 seams
9. https://github.com/ComposioHQ/composio/pull/3885 — try/catch+env ban
+ boundary ratchet
On stacked bases CI runs lint/build and the CLI Docker e2e job; unit
tests and typecheck are verified locally per PR and re-verified by full
CI when each PR is retargeted to `next`.
This PR:
- activates the descriptor-seam rule group:
`commands/command-introspection.ts` becomes the only module allowed to
import `CommandDescriptor`/`Usage` from `@effect/cli` — the redesign
seam the Effect CLI v4 migration will reimplement
- routes root command assembly, preflight parsing, help routing, and
value-option collection through the seam helpers instead of descriptor
walks
- replaces the Zod-backed tool-input adapter with an internal
`@composio/json-schema-to-effect-schema` compiler backed by the
eval-free `@cfworker/json-schema` interpreter
- normalizes the OpenAPI and Composio extensions accepted by the
previous validator, while preserving typed `ToolInputValidationError`
failures, field paths, multi-error reporting, and typo suggestions
- extends the descriptor and Zod boundaries to reject static imports,
dynamic `import()`, and `require()` bypasses
- adds compiler parity tests plus CLI regression coverage for multiple
simultaneous field and unknown-key failures
- introspection tests pin that the helpers yield identical descriptors
and usage strings
---
**Stack** (re-slice of https://github.com/ComposioHQ/composio/pull/3859;
each PR targets its parent and auto-retargets to `next` as parents
merge):
1. https://github.com/ComposioHQ/composio/pull/3877 — guidelines + inert
rule groups
2. https://github.com/ComposioHQ/composio/pull/3878 — `new Function()`
eval security fix
3. https://github.com/ComposioHQ/composio/pull/3879 — behavioral fix
pack
4. https://github.com/ComposioHQ/composio/pull/3880 — platform-imports
rule + migration
5. https://github.com/ComposioHQ/composio/pull/3881 — terminal-streams
rule + TerminalUI boundary
6. https://github.com/ComposioHQ/composio/pull/3882 — descriptor seam +
Zod→Schema ← **this PR**
7. https://github.com/ComposioHQ/composio/pull/3883 — test-tree lint +
deterministic suites
8. https://github.com/ComposioHQ/composio/pull/3884 — typed error
boundaries + v4 seams
9. https://github.com/ComposioHQ/composio/pull/3885 — try/catch+env ban
+ boundary ratchet
On stacked bases CI runs lint/build and the CLI Docker e2e job; unit
tests and typecheck are verified locally per PR and re-verified by full
CI when each PR is retargeted to `next`.
This PR:
- activates the terminal-streams rule group:
`process.stdout`/`process.stderr` are now ESLint-banned in CLI source,
with `services/terminal-ui.ts` as the sole stream boundary (that file is
itself held to the try/catch + `process.env` restrictions early)
- extends `TerminalUI` with `capabilities` (TTY/interactivity/decoration
detection) and a raw `error()` writer, replacing `utils/stdio.ts`
(deleted)
- makes analytics dispatch Effect-native and delivers detached telemetry
through `DetachedProcess`, staying non-fatal on failure
- consolidates runtime debug logging into a `RuntimeDebugLogger` service
and rebuilds `cli-main` around `showUpdateNotice`/telemetry as Effects
with errors reported via `ui.error`
- the update checker reads terminal capabilities from the service and
keeps its fire-and-forget contract
---
**Stack** (re-slice of https://github.com/ComposioHQ/composio/pull/3859;
each PR targets its parent and auto-retargets to `next` as parents
merge):
1. https://github.com/ComposioHQ/composio/pull/3877 — guidelines + inert
rule groups
2. https://github.com/ComposioHQ/composio/pull/3878 — `new Function()`
eval security fix
3. https://github.com/ComposioHQ/composio/pull/3879 — behavioral fix
pack
4. https://github.com/ComposioHQ/composio/pull/3880 — platform-imports
rule + migration
5. https://github.com/ComposioHQ/composio/pull/3881 — terminal-streams
rule + TerminalUI boundary ← **this PR**
6. https://github.com/ComposioHQ/composio/pull/3882 — descriptor seam +
zod→Schema
7. https://github.com/ComposioHQ/composio/pull/3883 — test-tree lint +
deterministic suites
8. https://github.com/ComposioHQ/composio/pull/3884 — typed error
boundaries + v4 seams
9. https://github.com/ComposioHQ/composio/pull/3885 — try/catch+env ban
+ boundary ratchet
On stacked bases CI runs lint/build and the CLI Docker e2e job; unit
tests and typecheck are verified locally per PR and re-verified by full
CI when each PR is retargeted to `next`.
This PR:
- activates the platform-imports rule group: `node:fs`, `node:path`,
`node:os`, and `child_process` are now ESLint-banned in CLI source
- migrates ~36 files to `FileSystem`/`Path`/`Command` from
`@effect/platform`, introducing the `NodeOs` and `DetachedProcess`
service boundaries and Effect-native run-companion module resolution
- keeps the sanctioned permanent boundaries (`node-os.ts`,
`detached-process.ts`, `run-subagent-output-mcp.ts`,
`cli-user-config.ts`, the child-process companion runtime) on inline `--
reason` disables
- files whose compliant rewrite belongs to a later slice carry temporary
inline disables naming the owning PR; a small sync bridge in
`run-companion-modules.ts` keeps the not-yet-migrated Promise-based
callers working until the typed-error slice removes it
- adds A-theme coverage: `NodeOs` platform info through Effect, session
artifacts via `FileSystem` with symlink and HOME-less handling,
skill-root discovery, and safe-output-path traversal guards
---
**Stack** (re-slice of https://github.com/ComposioHQ/composio/pull/3859;
each PR targets its parent and auto-retargets to `next` as parents
merge):
1. https://github.com/ComposioHQ/composio/pull/3877 — guidelines + inert
rule groups
2. https://github.com/ComposioHQ/composio/pull/3878 — `new Function()`
eval security fix
3. https://github.com/ComposioHQ/composio/pull/3879 — behavioral fix
pack
4. https://github.com/ComposioHQ/composio/pull/3880 — platform-imports
rule + migration ← **this PR**
5. https://github.com/ComposioHQ/composio/pull/3881 — terminal-streams
rule + TerminalUI boundary
6. https://github.com/ComposioHQ/composio/pull/3882 — descriptor seam +
zod→Schema
7. https://github.com/ComposioHQ/composio/pull/3883 — test-tree lint +
deterministic suites
8. https://github.com/ComposioHQ/composio/pull/3884 — typed error
boundaries + v4 seams
9. https://github.com/ComposioHQ/composio/pull/3885 — try/catch+env ban
+ boundary ratchet
On stacked bases CI runs lint/build and the CLI Docker e2e job; unit
tests and typecheck are verified locally per PR and re-verified by full
CI when each PR is retargeted to `next`.
## Review follow-up
- `DetachedProcess` now waits for Node’s `spawn` or `error` event, so
ENOENT/EACCES become typed failures instead of false success; focused
tests cover failure and success.
- the platform-import rule now rejects bare `fs`, `fs/*`, `os`, and
`path` specifiers as well as their `node:` forms; the two exposed path
imports now use Effect `Path`.
- run-companion self-repair again honors unprefixed `GITHUB_*` values
first, with `COMPOSIO_GITHUB_*` fallback and regression coverage for
both contracts.
Verification: CLI tests (95 files, 834 passed, 1 skipped), workspace
TypeScript typecheck, scoped ESLint (0 errors), CLI build, Prettier, and
`git diff --check`.