Skip to content

Build1 publisher3 min readPublished

Disabling an agent harness's retries leaves the OpenAI Node SDK's two default retries active

OpenAI's Node SDK sent three requests to a failing mock server because the agent harness above it leaves maxRetries at the SDK default of two. The one-attempt rule held only after a guard was locked onto globalThis.fetch, where a startup call had silently replaced the first one.

The Engineer · Build desk

Illustration accompanying Disabling an agent harness's retries leaves the OpenAI Node SDK's two default retries active

What happened

  • The omo-ai coding-agent harness ships with retries on, up to three plus a model fallback chain, and the developer's first fix was setting retry.enabled to false.
  • The harness builds its OpenAI Node SDK client without passing maxRetries, so the SDK's own default of two retries still applied underneath.
  • When a colleague agent ran the real binary end to end, the mock received four requests while the developer's fetch guard logged nothing.
  • Once the guard was fixed, its audit log showed a denied analytics flush to a PostHog host that the developer had not planned for.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A retry switch in harness config reaches only the harness, so a stack that builds an OpenAI Node client without an explicit maxRetries can send three times the requests a one-attempt policy allows.
  • decision Enforcing a one-attempt or byte-ceiling rule means picking one exit point and locking it, because startup code can reassign a guard that was only set as a plain global.
  • cost Library-level tests are weak evidence here: 11 passing tests missed the startup swap, so proving the rule takes an end-to-end run of the shipped binary against a counting mock.
  • exposure Shell tools that spawn curl bypass Node's fetch entirely, so a fetch-level guard leaves that traffic unmetered until a dedicated OS user and network rules are added.

A local mock that answers every call with HTTP 500 shows the default directly. The installed SDK alone delivered `[97, 97, 97]` to it, one request and two retries, and `[97]` once a guard sat in front [5]. The responsible line is `maxRetries ?? 2` in openai 6.26.0 [2][4]. The requirement was exactly one attempt, with no retry and no fallback model [1]. The post's author wrote that saying you turned retries off describes only one layer [18].

The stack is five packages deep, from omo-ai down to undici [1]. The author did not want to patch installed packages, so the guard went where every request has to pass: `globalThis.fetch`, preloaded with `NODE_OPTIONS=--import` [7]. It checks the origin, measures the serialized body and allows one request per process [7]. I'd make the same choice for a stack this deep, because each layer brings its own defaults. The author found the SDK's retry only by reading one layer below the harness [4].

The empty audit log on the real binary traces to one startup call. The harness configures an HTTP dispatcher and calls `undici.install()`, which in effect runs `globalThis.fetch = undici.fetch` [9]. So the guard loaded first and was then overwritten [9]. A colleague's diagnostic checked `fetch.name` at startup and saw the guard, because it ran before the replacement [10]. "Both of us were looking at the right variable at the wrong moment," the author wrote [19].

Version 0.2 locks the property itself. It calls `Object.defineProperty` on `globalThis` with `configurable: false`, a getter that always returns `guardedFetch` and a setter that only swaps the inner function the guard wraps [11]. `undici.install()` still runs, and the harness still gets undici's fetch, now behind the guard [11]. This is good engineering: it keeps the harness's dispatcher setup intact and needs no patched packages [7][11]. On the real binary with retries deliberately left on, v0.1 reproduced four requests and v0.2 allowed one, with the audit line's byte count equal to what the mock received [12]. The author's lesson was that "a guard you install at startup can be uninstalled at startup." [20]

The analytics flush was disclosed traffic. The tool prints a notice about anonymous usage telemetry and documents a `DO_NOT_TRACK=1` opt-out that its code reads [14]. Its statement that no prompts or paths are sent went unverified by the author [14].

Proving the byte ceiling took a second attempt. A first test measured one request and replayed it with the ceiling one byte lower. It failed because the same command produced bodies between 107,154 and 107,162 bytes, since the harness includes per-run values [15]. That spread is 8 bytes [2]. Now each audit line compares that request's bytes with the ceiling, and unit tests cover the 131,072 / 131,073 edge [16].

Every count in the post comes from a local mock with a fake key, and no request reached a real provider [6]. For the three-request result to transfer, a stack has to build its client on the openai Node SDK without setting `maxRetries`; the post measured version 6.26.0 [2][4]. The post does not break down which layer issued each of the four requests on the real binary [12].

What to watch

  • Whether omo-ai starts passing maxRetries to the OpenAI client, or maps retry.enabled: false to zero SDK retries.
  • A per-layer breakdown of the four requests the real binary sent, which would show how harness and SDK retries combine.
  • Results from the OS-level boundary for child processes, and what it catches that the fetch guard cannot see.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories