Codex CLI exits abruptly on `rate_limit_exceeded`; should back-off & retry instead of closing

Resolved 💬 9 comments Opened Apr 27, 2025 by lucasrod Closed Aug 7, 2025
💡 Likely answer: A maintainer (tibo-openai, collaborator) responded on this thread — see the highlighted reply below.

What version of Codex is running?

v0.1.2504161551

Which model were you using?

o4-mini

What platform is your computer?

Darwin 24.4.0 arm64 arm

What steps can reproduce the bug?

  1. Run a long Codex CLI command that streams a large prompt + completion (≈70 k tokens).

``bash
codex 'explain this huge code base' --files src/**/*.py
``

  1. Wait until total tokens in the last minute exceed 200 000 TPM.

What is the expected behavior?

CLI should:

  • Detect the rate_limit_exceeded error.
  • Wait for the retry-after window (OpenAI returns 2.515 s in this case) with exponential back-off.
  • Resume the same request or transparently re-send it, keeping the stream open.
  • Exit with a non-zero code only after exhausting configured retries.

What do you see instead?

The CLI throws an uncaught exception and terminates:

e [Error]: Rate limit reached for o4-mini … Limit 200000, Used 136502, Requested 71884 …
    at file:///opt/homebrew/lib/node_modules/@openai/codex/dist/cli.js:445:1514
Node.js v23.11.0

All partial output is lost; the user must re-run the command manually.

Additional information

  • Running latest CLI via npm i -g @openai/codex.
  • No custom flags (--max-retries defaults to 0).
  • It would be safer to set a default retry policy (e.g., 3 attempts, exponential 2 → 4 → 8 s).
  • See full stack trace in the console log attached above.

View original on GitHub ↗

9 Comments

Fredrik-C · 1 year ago

It is sad that this is still an issue. Should be priority one to fix the crashes since you loose all context and wasted tokens.

https://github.com/openai/codex/issues/137
https://github.com/openai/codex/issues/157

tibo-openai collaborator · 1 year ago

The version number above indicates that you are running an old version. This was fixed a while ago, please ensure that you are running the latest version here: https://github.com/openai/codex/blob/main/CHANGELOG.md#012504251709

You should see

$ codex --version
0.1.2504251709
e-e-e · 1 year ago

I am still getting this issue in version 0.1.2504221401 which supposedly includes the commit (https://github.com/openai/codex/pull/506) that was to fix these issues.

For example:

w0 [Error]: 400 No tool output found for function call call_wyfQutQpE6tjvubx5WT84deI.
    at e.generate (file:///Users/xxxxx/.n/lib/node_modules/@openai/codex/dist/cli.js:473:4010)
    at Rr.makeStatusError (file:///Users/xxxxx/.n/lib/node_modules/@openai/codex/dist/cli.js:477:7623)
    at Rr.makeRequest (file:///Users/xxxx/.n/lib/node_modules/@openai/codex/dist/cli.js:477:8555)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async fE.run (file:///Users/xxxxxx/.n/lib/node_modules/@openai/codex/dist/cli.js:493:198) {
  status: 400,
  headers: {
    'alt-svc': 'h3=":443"; ma=86400',
    'cf-cache-status': 'DYNAMIC',
    'cf-ray': '936a92bb4b5ba96d-SYD',
    connection: 'keep-alive',
    'content-length': '184',
    'content-type': 'application/json',
    date: 'Sun, 27 Apr 2025 01:33:41 GMT',
    'openai-organization': 'personal-ayybqx',
    'openai-processing-ms': '264',
    'openai-version': '2020-10-01',
    server: 'cloudflare',
    'set-cookie': XXXXX,
    'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
    'x-content-type-options': 'nosniff',
    'x-request-id': 'req_652445c72dbde625d65bb1cdc06945c0'
  },
  request_id: 'req_652445c72dbde625d65bb1cdc06945c0',
  error: {
    message: 'No tool output found for function call call_wyfQutQpE6tjvubx5WT84deI.',
    type: 'invalid_request_error',
    param: 'input',
    code: null
  },
  code: null,
  param: 'input',
  type: 'invalid_request_error'
}

When I look into the logs I see it was because I exceeded quota with the offending request.
This is really bad UX as it blows away context and costs the user money without giving value.
I am kinda shocked by the state of this tool. It feels very broken.

andrewleech · 1 year ago

This relates to pretty much any "failed" api request, they all crash out.
Error 400 / 500 / etc should all be handled similarly to rate limiting, retry with backoff.

IMO The only one that should likely be fail fast is an invalid key (as opposed to a rate limited key)

Many other API error responses are fleeting, either their server needs you to retry because they're flooded, or the LLM needs to retry with slightly different request/response to come up with a working message.

rivershah · 1 year ago

Still there in:

codex --version
0.1.2504301751
tralston · 1 year ago

Still crashing (v0.1.2505140839):

file:///Users/***/.nvm/versions/node/v22.14.0/lib/node_modules/@openai/codex/dist/cli.js:540
`,"\r"]);n_.NEWLINE_REGEXP=/\r\n|[\n\r]/g;function qFe(e,t){for(let a=t??0;a<e.length;a++){if(e[a]===10)return{preceding:a,index:a+1,carriage:!1};if(e[a]===13)return{preceding:a,index:a+1,carriage:!0}}return null}function dle(e){for(let n=0;n<e.length-1;n++){if(e[n]===10&&e[n+1]===10||e[n]===13&&e[n+1]===13)return n+2;if(e[n]===13&&e[n+1]===10&&n+3<e.length&&e[n+2]===13&&e[n+3]===10)return n+4}return-1}m();function oB(e){if(e[Symbol.asyncIterator])return e;let t=e.getReader();return{async next(){try{let r=await t.read();return r?.done&&t.releaseLock(),r}catch(r){throw t.releaseLock(),r}},async return(){let r=t.cancel();return t.releaseLock(),await r,{done:!0,value:void 0}},[Symbol.asyncIterator](){return this}}}var Qd=class e{constructor(t,r){this.iterator=t,this.controller=r}static fromSSEResponse(t,r){let n=!1;async function*a(){if(n)throw new Error("Cannot iterate over a consumed stream, use `.tee()` to split the stream.");n=!0;let i=!1;try{for await(let o of VFe(t,r))if(!i){if(o.data.startsWith("[DONE]")){i=!0;continue}if(o.event===null||o.event.startsWith("response.")||o.event.startsWith("transcript.")){let c;try{c=JSON.parse(o.data)}catch(u){throw console.error("Could not parse message into JSON:",o.data),console.error("From chunk:",o.raw),u}if(c&&c.error)throw new bi(void 0,c.error,void 0,lB(t.headers));yield c}else{let c;try{c=JSON.parse(o.data)}catch(u){throw console.error("Could not parse message into JSON:",o.data),console.error("From chunk:",o.raw),u}if(o.event=="error")throw new bi(void 0,c.error,c.message,void 0);yield{event:o.event,data:c}}}i=!0}catch(o){if(o instanceof Error&&o.name==="AbortError")return;throw o}finally{i||r.abort()}}return new e(a,r)}static fromReadableStream(t,r){let n=!1;async function*a(){let o=new n_,c=oB(t);for await(let u of c)for(let f of o.decode(u))yield f;for(let u of o.flush())yield u}async function*i(){if(n)throw new Error("Cannot iterate over a consumed stream, use `.tee()` to split the stream.");n=!0;let o=!1;try{for await(let c of a())o||c&&(yield JSON.parse(c));o=!0}catch(c){if(c instanceof Error&&c.name==="AbortError")return;throw c}finally{o||r.abort()}}return new e(i,r)}[Symbol.asyncIterator](){return this.iterator()}tee(){let t=[],r=[],n=this.iterator(),a=i=>({next:()=>{if(i.length===0){let o=n.next();t.push(o),r.push(o)}return i.shift()}});return[new e(()=>a(t),this.controller),new e(()=>a(r),this.controller)]}toReadableStream(){let t=this,r,n=new TextEncoder;return new $O({async start(){r=t[Symbol.asyncIterator]()},async pull(a){try{let{value:i,done:o}=await r.next();if(o)return a.close();let c=n.encode(JSON.stringify(i)+`


e [Error]: Request too large for o3 in organization *** on tokens per min (TPM): Limit 30000, Requested 31608. The input or output tokens must be reduced in order to run successfully. Visit https://platform.openai.com/account/rate-limits to learn more.
    at e.a [as iterator] (file:///Users/***/.nvm/versions/node/v22.14.0/lib/node_modules/@openai/codex/dist/cli.js:540:1514)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async xE.run (file:///Users/***/.nvm/versions/node/v22.14.0/lib/node_modules/@openai/codex/dist/cli.js:557:2801) {
  status: undefined,
  headers: undefined,
  request_id: undefined,
  error: {
    type: 'tokens',
    code: 'rate_limit_exceeded',
    message: 'Request too large for o3 in organization *** on tokens per min (TPM): Limit 30000, Requested 31608. The input or output tokens must be reduced in order to run successfully. Visit https://platform.openai.com/account/rate-limits to learn more.',
    param: null
  },
  code: 'rate_limit_exceeded',
  param: null,
  type: 'tokens'
}

Node.js v22.14.0

$ codex --version
0.1.2505140839
mdave752 · 1 year ago

Still rate_limits_exceeded on version 0.1.2505172129 with model gpt-4.1.

Temporary fix I found for this that works is that you can ask codex or make yourself a simple script that saves user and codex assistant prompts and responses to log files, and when it crashes and you have to start a new session give the log file to codex so it knows where you left off.

I requested to run it every time I send a prompt and every time codex gave me an answer.

This is the script codex made for my project:

import sys
import json
import os
from datetime import datetime

HISTORY_FILE = os.path.join(os.path.dirname(__file__), 'conversation_history.jsonl')

def log_message(sender, message):
    entry = {
        'timestamp': datetime.utcnow().isoformat(),
        'sender': sender,
        'message': message
    }
    with open(HISTORY_FILE, 'a', encoding='utf-8') as f:
        f.write(json.dumps(entry, ensure_ascii=False) + '\n')

def show_history(limit=20):
    if not os.path.exists(HISTORY_FILE):
        print('No conversation history found.')
        return
    with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
        lines = f.readlines()
        lines = lines[-limit:]
        for line in lines:
            entry = json.loads(line)
            ts = entry['timestamp']
            sender = entry['sender']
            msg = entry['message']
            print(f"[{ts}] {sender.upper()}: {msg}")

def usage():
    print("Usage:")
    print("  python local_convo_history.py log_message <sender> <message>")
    print("  python local_convo_history.py show_history [limit]")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        usage()
        sys.exit(1)
    cmd = sys.argv[1]
    if cmd == "log_message":
        if len(sys.argv) < 4:
            usage()
            sys.exit(1)
        sender = sys.argv[2]
        message = ' '.join(sys.argv[3:])
        log_message(sender, message)
    elif cmd == "show_history":
        limit = 20
        if len(sys.argv) > 2:
            try:
                limit = int(sys.argv[2])
            except Exception:
                print("Invalid limit, using default 20.")
        show_history(limit)
    else:
        usage()
rivershah · 1 year ago

How is this issue still open and not fixed?

codex-maintainers · 11 months ago

Thank you for the feedback! Please try again on the latest version of Codex CLI. If the issue persists, we'd be happy to take a look at a PR to the codex-rs/ implementation. For more details, see our update: https://github.com/openai/codex/discussions/1174.