DEV Community

Sushil
Sushil

Posted on

Composio 0.28 Stops Retrying Writes. I Counted the Requests to See What Actually Changed

The @composio/core 0.28.0 changelog has a line that sounds like it changes how every agent tool call behaves: "POST and PATCH requests, including tool execution and trigger creation, are now sent once instead of being retried on a lost response or a retryable status." The reason is fair. A write can finish on the server after its response gets lost, and a blind retry sends that email twice.

But if you use composio.tools.execute(), it's not a new behavior for you. I pointed 0.27.0 and 0.28.0 at a local server that answers every POST with a 503 and counted what came in.

What I ran

A tiny Node HTTP server logs each request. It answers the tool and trigger-type lookups (GETs) with a fake tool definition and every POST with 503. Each SDK version runs against it with baseURL: 'http://127.0.0.1:4010' and COMPOSIO_ALLOW_INSECURE_HTTP=1. I ran it on Node 22.23.4 with @composio/core 0.27.0 (which pulls @composio/client 2.0.0-rc.19) and 0.28.0 (@composio/client 2.0.1).

Call 0.27.0 POSTs 0.28.0 POSTs
composio.tools.execute() 1 1
composio.triggers.create() 3 1
composio.getClient().tools.execute() (raw client) 3 1
Raw client with { maxRetries: 2 } 3 3

The GET lookups before each write were still retried in both versions. When I made the tool lookup return 503 too, the SDK sent it three times before giving up.

Tool execution already went out once

The high-level tools.execute() stopped retrying a long time ago. The changelog for 0.14.0 says it and tools.proxyExecute route through a client "built with maxRetries: 0", and 0.23.0 did the same for session.execute() and session proxy calls. In the 0.27.0 build you can still see the helper that does it, with a comment pointing at issue #3654 ("Retried tools.execute / tools.proxy can duplicate non-idempotent side effects").

So if your agent only runs tools through tools.execute(), sessions, or a provider wrapper, the upgrade doesn't change how often a failed tool call hits the API.

Where 0.28 does change things

Two places in my test:

triggers.create(). On 0.27.0, a 503 on the upsert meant three POSTs. Now it's one. If you had a flaky network and were quietly relying on the SDK to get a trigger created, you'll see that error now instead.

The raw client from getClient(). Any POST or PATCH you send through it was retried twice by default on 0.27.0. On 0.28.0 it goes once, unless that specific call passes maxRetries:

import { Composio } from '@composio/core';

const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
const client = composio.getClient();

// 0.28: sent once. On a 503 or lost response you get the error.
await client.tools.execute('GITHUB_CREATE_AN_ISSUE', {
  user_id: 'user_1',
  arguments: { owner: 'acme', repo: 'app', title: 'Bug' },
});

// Opt back in for this call only. Only do this for writes that are safe to repeat.
await client.tools.execute(
  'GITHUB_CREATE_AN_ISSUE',
  { user_id: 'user_1', arguments: { owner: 'acme', repo: 'app', title: 'Bug' } },
  { maxRetries: 2 },
);
Enter fullscreen mode Exit fullscreen mode

I confirmed the second form sends three attempts against the mock. I didn't run either call against the real Composio API; the numbers above come from the local 503 server only, and I didn't simulate a dropped connection or a timeout.

The raw client also had breaking changes

If you use getClient(), read the rest of the 0.28.0 entry before upgrading. The client no longer has request(), so you call get, post, patch, put or delete instead. Its resources are plain objects now, so the resource classes and Composio.Tools-style statics are gone, and .js/.mjs subpath imports from @composio/client no longer resolve. One more thing: @composio/core 0.28.0 declares "node": ">=22.22.3" in its engines field, so I ran these tests on Node 22.

What to do about it

Don't add a retry loop around writes to get the old behavior back. That's the exact duplicate-write bug the change is avoiding. If a write fails with an ambiguous error, check whether it happened (list the trigger, look for the created issue) before you send it again, or give the write an idempotency key your own code can check.

For reads, nothing changed. GETs still get retried for you.

Are you calling the raw client anywhere in your agent code? That's the place I'd grep first.

Top comments (0)