## Summary
- Recreate the generated Changesets release PR from
`changeset-release/next` after #3372 was closed.
- Keep `@composio/core` on the generated minor bump to `0.9.0`.
- Manually correct provider package releases from the generated `1.0.0`
major bump down to `0.9.0` minor bumps.
- Add provider changelog minor notes explaining the versions are aligned
with `@composio/core@0.9.0` and do not represent provider API breaks.
## Context
- Addresses the Bugbot finding on #3372:
https://github.com/ComposioHQ/composio/pull/3372#discussion_r3202024169
- This follows the prior manual correction pattern used when Changesets
tried to promote provider packages to `1.0.0` because a `0.x` core peer
dependency received a minor bump.
## Validation
- `git diff --check`
- Verified no provider package `package.json` or changelog heading
remains at `1.0.0`.
- Spot-checked the PR diff shows provider packages moving from `0.8.1`
to `0.9.0` while their `@composio/core` peer dependency moves to
`0.9.0`.
## Summary
- Remove the Python package name from the TypeScript changeset
frontmatter.
- Keeps the `@composio/core` minor bump intact so pending patch/minor
changesets collapse into one TS minor release.
## Root Cause
The TypeScript Changesets release job runs against the pnpm workspace.
`.changeset/session-update-method.md` referenced `composio`, which is
the Python package name and not a pnpm workspace package, so `changeset
version` failed with `Found changeset session-update-method for package
composio which is not in the workspace`.
## Validation
- `git diff --check`
- Confirmed no remaining `.changeset` entry references `"composio"` as a
package name.
Note: a full local `pnpm changeset version` dry run was blocked in the
temp worktree by missing `node_modules`; the failure is static and this
removes the invalid package reference.
## Summary
- Bump the root Python SDK package from `0.12.0` to `0.13.0`.
- Bump all Python provider packages from `0.12.0` to `0.13.0`.
- Keep this PR release-only; no publish tag has been created.
## Notes
- Version bumps were generated with the repo bump script: `cd python &&
uv run python scripts/bump.py --minor`.
- Publishing still happens later by pushing the `py@0.13.0` tag, per
`.github/workflows/py.release.yml`.
- This mirrors the minor-release intent of TypeScript PR #3367, using
the Python release flow instead of changesets.
## Validation
- `git diff --check`
- Checked that the diff only updates version fields in
`python/pyproject.toml` and provider `pyproject.toml` / `setup.py`
files.
- Did not run tests because this PR only changes package versions.
# Description
Adds five FAQs to the Slack toolkit FAQ snippet
(`docs/content/toolkits/faq/slack.md`) covering the most common Slack
OAuth install/approval friction points:
1. Why users see "This app isn't listed in the Slack Marketplace…"
during install
2. Whether workspace owner is required to install the app
3. Why Slack asks the user to submit a request during auth
4. How a workspace member can complete the connection without
permissions/approvals
5. The recommended solution (custom OAuth app)
These FAQs are placed right after the existing "How do I set up custom
OAuth credentials for Slack?" entry, since they're all about the OAuth
install flow and the resolution path consistently points back to using a
custom OAuth app.
Format follows the existing snippet style: plain markdown, `##
Question?` headers, paragraph or short bullet-list answers, `**bold**`
for UI/setting names, relative/external links matching what's already in
the file. No frontmatter (matches the rest of `content/toolkits/faq/`).
# How did I test this PR
- Read existing FAQ snippets (`slack.md`, `gmail.md`, `notion.md`) to
match style — short headers, `**bold**` for Slack setting names, no
Fumadocs components, ends with `---`
- Verified the file structure is unchanged: 5 new `## Question?`
sections inserted between the existing custom-OAuth question (line 9)
and the Slack-vs-Slackbot question; closing `---` separator still
present at EOF
- All links in the new content point to
`https://composio.dev/auth/slack`, which already exists in the same file
— no new link surface introduced
- Confirmed `bun run scripts/validate-links.ts` failure is pre-existing
on `origin/next` (sandbox dependency mismatch on
`fumadocs-core/content/md/frontmatter`), not caused by this change
- No code blocks added, so Twoslash type-check is not affected
Triggered by: uday@composio.dev | Source: slack
Session:
https://zen-api-production-4c98.up.railway.app/dashboard/#/chat/zen-7f4c8629aad8
# Description
Adds a one-line callout to the trigger-subscription docs page clarifying
that user webhook endpoints must be publicly reachable. Composio's
outbound webhook delivery runs on Cloudflare Workers, which use dynamic
egress IPs — so IP allowlisting and private/VPN-only endpoints will
silently drop webhook payloads (both `composio.trigger.message` and
`composio.connected_account.expired`).
The callout points readers to signature verification as the intended
authentication mechanism instead.
Placed on `subscribing-to-events.mdx` directly after the `POST
/webhook_subscriptions` example, where users are actually setting their
webhook URL.
# How did I test this PR
- Verified the claim by tracing webhook egress in `hermes`: cloud
delivery flows through `defaultDeliveryService` →
`sendComposioProxyRequest` → Cloudflare Workers external-proxy
(`/workspace/hermes/wrangler/external-proxy/`); self-hosted goes through
`directHttpDeliveryService`. Both `TRIGGER_MESSAGE_EVENT_TYPE` and
`CONNECTION_EXPIRED_EVENT_TYPE` share this path
(`apps/apollo/src/lib/events/handlers/webhook/handler.ts:86-90,
241-255`). No static-IP infrastructure exists for the Workers egress.
- Confirmed the linked `/docs/webhook-verification` route already exists
in the same file (line 30), so no new links were introduced.
- Content-only MDX change (no TypeScript code blocks added), so twoslash
type-checking is unaffected.
# Description
Bumps the `packageManager` pin from `pnpm@10.28.0` to `pnpm@10.28.2`,
the smallest release that fixes
[**CVE-2026-24056**](https://nvd.nist.gov/vuln/detail/CVE-2026-24056) —
a symlink
attack in pnpm where installing a malicious `file:` (directory) or
`git:`
dependency that contains a symlink to an absolute path causes pnpm to
copy that target file's contents (e.g. `~/.ssh/id_rsa`, `/etc/passwd`)
into `node_modules`, enabling local data exfiltration on developer
machines and CI runners.
Practical risk for this repo is **LOW** — we only install
registry-pinned dependencies with `minimumReleaseAge: 4320` (72-hour
cooldown) configured in `pnpm-workspace.yaml`, so the attacker would
need a malicious package to (a) be added as a dependency, and (b) sit
unnoticed for 72h. The fix is nevertheless a one-line defensive bump
that aligns this repo with the rest of the monorepo:
| repo | pnpm version | status |
|--------------------|-------------|--------|
| hermes | `10.33.0` | safe |
| composio_dashboard | `10.28.2` | safe |
| frontend | `10.28.2` | safe |
| **composio (this PR)** | `10.28.0` -> `10.28.2` | **fix** |
| metrics.composio.io | `10.18.2` | flagged separately (no CI) |
Discovered during the daily Zen security audit cron.
# How did I test this PR
- Diff is a single character bump (`10.28.0` -> `10.28.2`) in
`package.json` `packageManager`. No code, lockfile, or workflow
changes — `pnpm install` will simply use the newer pnpm via Corepack.
- Verified `composio_dashboard` and `frontend` already pin
`pnpm@10.28.2`
successfully across their CI workflows.
- No GHA file modified, so the pnpm-version reference in
`ts/docs/internal/release.md` (per `CLAUDE.md` maintenance task) does
not need to be updated by this PR.
---
Triggered by: dhawal@composio.dev | Source: cron-48e51eab745f
Session:
https://zen-api-production-4c98.up.railway.app/dashboard/#/chat/zen-cron-16acec0fd729
Origin: cron-48e51eab745f /
[zen-cron-16acec0fd729](https://zen-api-production-4c98.up.railway.app/dashboard/#/chat/zen-cron-16acec0fd729)
## Summary
- Fixes SDK custom-tool mapping for custom toolkits that reuse the same
child tool slug, e.g. `APP_A.VERSION` and `APP_B.VERSION`.
- Adds toolkit-qualified lookup (`toolkit + original slug`) while
keeping bare original-slug lookup only when it is unambiguous.
- Throws a client-side ambiguity error for manual
`session.execute('VERSION')` when multiple custom toolkit tools share
`VERSION`, instead of falling through to backend execution.
- Keeps `session.customToolkits()` / `session.customTools()` mapped to
the backend-assigned final slugs for duplicate child names.
## Repro / validation
Validated locally with a temporary Tool Router script (not committed)
using two dummy custom toolkits that both define `VERSION`:
- Current `next` SDK fails before high-level use with `Custom tool slug
collision: original slug "VERSION" maps to multiple final slugs...`.
- Tool Router backend accepts the same payload and returns both
`LOCAL_DUMMY_APP_A_REPRO_VERSION` and `LOCAL_DUMMY_APP_B_REPRO_VERSION`.
- This branch builds the SDK map successfully and high-level SDK
execution routes each final slug to its own internal handler:
- `LOCAL_DUMMY_APP_A_REPRO_VERSION` -> `{ app: "A", version: "1.0.0" }`
- `LOCAL_DUMMY_APP_B_REPRO_VERSION` -> `{ app: "B", version: "2.0.0" }`
## Tests
- `pnpm --filter @composio/core typecheck`
- `pnpm --filter @composio/core exec vitest run
test/models/customTool.test.ts test/models/toolRouter.test.ts
test/models/customToolRouting.test.ts`
## Summary
Follow-up to #3358. The `.transform()` added on the `connectedAccounts`
Zod schema made the inferred TypeScript type resolve to the
**post-transform output** (`Record<string, string[]>`), so TS users
still hit `error TS2322: Type 'string' is not assignable to type
'string[]'` even though the runtime accepted strings. The runtime fix
landed; the public TS surface didn't follow.
This PR moves the coercion out of the schema and into
`ToolRouter.create`, mirroring the Python implementation. The public
type is now `Record<string, string | string[]>` and runtime behavior is
unchanged.
## Why CI didn't catch this in #3358
The PR's own test file (`toolRouter.test.ts`) already assigns a `string`
to `ToolRouterCreateSessionConfig`, but `ts/packages/core/tsconfig.json`
has `"include": ["src/**/*.ts"]` — `test/` is not typechecked. So `tsc
--noEmit clean` was technically true while masking the issue. Verified
empirically by dropping a typecheck file into `src/` on `next`:
```
src/__type_check.ts(8,7): error TS2322: Type 'string' is not assignable to type 'string[]'.
```
After this PR, the same file compiles cleanly.
## Changes
- **`ts/packages/core/src/types/toolRouter.types.ts`**: drop
`.transform()` from the `connectedAccounts` schema; `z.infer` now
exposes `Record<string, string | string[]>`.
- **`ts/packages/core/src/models/ToolRouter.ts`**: coerce `string →
[string]` inside `create` before sending to the backend (which expects
arrays). Preserves the original `connected_accounts: undefined` behavior
when the field is omitted (the parent schema's `.partial()` strips the
`default({})`).
- **`ts/packages/core/src/types/toolRouter.types.ts`**: fix stale
`@param {Record<string, string>}` JSDoc on the same field.
- **`.changeset/v31-connected-accounts-array.md`**: drop the inaccurate
*"Bumps `@composio/client` to `0.1.0-alpha.70`"* line — that bump landed
in a separate commit on `next` (`7c1443838`), not in #3358's diff (`git
diff 2bbd791de^1 2bbd791de` shows zero `pnpm-lock.yaml`/`package.json`
changes).
- **`.changeset/fix-connected-accounts-string-type.md`**: new `patch`
changeset for `@composio/core`.
## Test plan
- [x] `pnpm exec tsc --noEmit -p tsconfig.json` clean on
`@composio/core`
- [x] `pnpm exec vitest run` — 883/883 tests pass (incl. 110 in
`toolRouter.test.ts`)
- [x] `pnpm prettier --check` clean on touched files
- [x] Spot-checked: assigning `{ connectedAccounts: { gmail: 'ca_xxx' }
}` to `ToolRouterCreateSessionConfig` now typechecks; assigning the
array form still typechecks.
The Zod `.transform()` added in #3358 made the inferred type of
`ToolRouterCreateSessionConfig['connectedAccounts']` resolve to
`Record<string, string[]>` (the post-transform output), so TypeScript
users still hit `Type 'string' is not assignable to type 'string[]'`
even though the runtime accepted strings. The runtime fix landed; the
public TS surface didn't follow.
Drop the schema transform and coerce inside `ToolRouter.create` instead,
mirroring the Python implementation. The public type is now
`Record<string, string | string[]>` and behavior is unchanged.
While here:
- fix the JSDoc on `connectedAccounts` (was `Record<string, string>`)
- drop the inaccurate `@composio/client 0.1.0-alpha.70` bump line from
the v31-connected-accounts-array changeset (this PR didn't perform
that bump; it landed in a separate commit on `next`)
Same pattern as ToolRouterCreateSessionConfigSchema — own Zod schema
with no defaults and nullable clearing fields. No longer references
create config types that have incompatible defaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Description
Apollo (hermes#9550) added a new \`REVOKED\` value to the
connected-account status enum, alongside the existing \`INITIALIZING |
INITIATED | ACTIVE | FAILED | EXPIRED | INACTIVE\`. The Apollo
public-API response schema is \`z.nativeEnum(ConnectionStatusEnum)\`, so
any list / get / status-update endpoint can now return \`REVOKED\` and
clients must handle it.
This PR fixes every hand-written place in the TS and Python SDKs that
hard-coded the prior 6-value set and would either (a) fail-fast against
\`REVOKED\` (Zod / Effect Schema parse error) or (b) silently
misclassify it.
## TypeScript SDK — BREAKING fixes
| File | Change | Severity |
|---|---|---|
| \`ts/packages/core/src/types/connectedAccounts.types.ts\` | Add
\`REVOKED\` to \`ConnectedAccountStatuses\` and
\`ConnectedAccountStatusSchema\` (\`z.enum\`) | BREAKING —
\`z.enum().parse()\` would throw on responses containing the new value |
| \`ts/packages/cli/src/models/connected-accounts.ts\` | Add \`REVOKED\`
to Effect \`Schema.Literal(...)\` for \`ConnectedAccountItem.status\` |
BREAKING — \`Schema.Literal()\` enforces literal matching |
| \`ts/packages/cli/src/services/tool-router-session-connections.ts\` |
Add \`'REVOKED'\` case to the \`normalizeConnectedAccountStatus\` switch
| BREAKING — silent fall-through to \`'INACTIVE'\` masked the new status
|
| \`ts/packages/cli/src/services/composio-clients.ts\` | Replace the
explicit 6-value cast with a generic that resolves to whatever the
Stainless client currently accepts | Forwards \`REVOKED\` to Apollo;
auto-tracks future Stainless regenerations |
| \`ts/packages/core/src/models/ConnectedAccounts.ts\` | Cast
\`statuses\` to the Stainless params type so \`REVOKED\` (already
accepted by Apollo) flows through until \`@composio/client\` is
regenerated | Adapter shim; remove once \`@composio/client\` is
regenerated |
## Python SDK — DEGRADED fixes
| File | Change | Severity |
|---|---|---|
| \`python/composio/core/models/webhook_events.py\` | Add \`REVOKED\` to
\`ConnectionStatusEnum\` | DEGRADED — code that checked \`status in
ConnectionStatusEnum\` would have failed to recognise the new value |
| \`python/composio/core/models/connected_accounts.py\` |
\`ConnectionRequest.wait_for_connection\` now fails fast on terminal
non-ACTIVE states (\`FAILED\` / \`EXPIRED\` / \`INACTIVE\` /
\`REVOKED\`) instead of looping until the 60s timeout | DEGRADED —
REVOKED used to look identical to "still pending" until the timeout
fired |
The Stainless-generated \`@composio/client\` (TS) and
\`composio_client\` (Python) packages will pick up \`REVOKED\`
automatically when regenerated; this PR covers the hand-written SDK code
only.
# How did I test this PR
- \`pnpm typecheck\` (root) — clean (\`9 successful, 9 total\`)
- \`pnpm test\` (root) — 694 passed, 1 skipped (75 test files); examples
validation \`Validated 22 example packages\`
- Python: \`ruff check\` and \`ruff format --check\` clean on both
edited files
- Smoke-tested Python imports + enum membership:
\`\`\`
PYTHON: imports OK, REVOKED present: REVOKED
\`\`\`
Triggered by: sarthak@composio.dev | Source: web
Session:
https://zen-api-production-4c98.up.railway.app/dashboard/#/chat/zen-14dc825802da
Also makes multiAccount fields optional in ToolRouterUpdateSessionConfig
(PATCH semantics) and adds a PATCH-safe transform that doesn't inject
create-time defaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drop manual camelCase→snake_case transformation and custom type
definitions. Use session_patch_params types from composio_client and
pass kwargs straight through to client.tool_router.session.patch().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three commands (`connected-accounts.list`, `connections.list`,
`connections.remove`) carried verbatim copies of the same 13-line
`Effect.catchTag('ParseError', ...)` forward-compat guard. Future
changes to the warning message or fallback semantics would have to
touch all three sites in lockstep.
Lift the guard into `src/effects/decode-connected-account-list.ts` so
each call site collapses to a single `yield*`. The helper carries the
WHY (forward-compat with Apollo enum additions) and the safety
justification for the cast in one place.
Surfaced by Cursor Bugbot on commit afb33deef.
Custom tools are set at create time only — PATCH can't change them,
so recomputing the preloaded slugs list after update is dead code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Route TypeScript `session.use()` through `/attach` even when no custom
tools are supplied
- Apply the same `/attach` behavior to Python `ToolRouter.use()`
- Update focused TypeScript/Python tests and the unreleased changeset
note
## Tests
- `pnpm --dir ts/packages/core test test/models/toolRouter.test.ts`
- `../.venv/bin/ruff check composio/core/models/tool_router.py
tests/test_tool_router.py` from `python/`
- `../.venv/bin/python -m pytest tests/test_tool_router.py` from
`python/`
Stainless client already validates SessionPatchParams. Replace the
ToolRouterUpdateSessionConfigSchema with a plain interface and coerce
connectedAccounts string→string[] in the transform layer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix stale preloadedCustomToolSlugs after update by recomputing them
- Add ToolRouterUpdateSessionConfigSchema for runtime validation
- Add PATCH-safe transform variants that don't inject defaults
- Add 11 unit tests for transformToolRouterUpdateParams
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mirrors the TypeScript session.update() — calls the existing PATCH
endpoint and mutates the session's preload config in-place.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exposes the existing PATCH /api/v3/tool_router/session/{id} endpoint
through the SDK so users can modify session config after creation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Net −56 lines of comment prose. Earlier commits added multi-paragraph
blocks explaining each forward-compat guard, sentinel choice, and
schema-bypass cast — most of that is rederivable from the surrounding
code. Keep one-line WHY notes where the intent is non-obvious; drop
the prose elsewhere.
`test_wait_for_connection_fails_fast_on_terminal_status` only patched
`time.sleep`, not `time.time`. A stuck CI runner could in principle
exhaust the 10s deadline before the first `retrieve()` call resolves,
in which case the test would assert against a
`ComposioSDKTimeoutError` instead of the `SDKError` it's actually
exercising. Match the monotonic-counter pattern used by the sibling
tests at lines 137 and 170 to remove that flake surface.
Surfaced by kieran-python review of PR #3344 (P3#4).
## Summary
Adds missing changesets for #3350 and updates `connectedAccounts` to
accept both `string` and `string[]` (non-breaking).
### Changesets (`@composio/core` minor)
1. **`v31-customs-use-attach`** — `composio.use()` with custom tools via
v3.1 `/attach`
2. **`v31-connected-accounts-array`** — `connectedAccounts` accepts
`string | string[]`, coerced to array
### connectedAccounts coercion
- TS: Zod schema accepts `string | string[]` per toolkit, `.transform()`
normalizes to `string[]`
- Python: type is `Union[str, List[str]]`, coerced to list before
passing to the API
- Existing callers passing `{ gmail: "ca_xxx" }` continue to work
without changes
- Only one account per toolkit is allowed when multi-account mode is
disabled
## Test plan
- [x] TS: 109/109 `toolRouter.test.ts` pass (tests both string and array
forms)
- [x] Python: 65/65 `test_custom_tools.py` pass
- [x] `tsc --noEmit` clean
- [x] `ruff` clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)