Skip to content

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

Closed

Description

@lucasrod

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).
    codex 'explain this huge code base' --files src/**/*.py
  2. 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.

Activity

  1. Fredrik-C commented on Apr 28, 2025

    @Fredrik-C

    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.

    #137
    #157

  2. tibo-openai commented on Apr 29, 2025

    @tibo-openai
    Collaborator

    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
    
  3. e-e-e commented on Apr 29, 2025

    @e-e-e

    I am still getting this issue in version 0.1.2504221401 which supposedly includes the commit (#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.

  4. andrewleech commented on Apr 30, 2025

    @andrewleech

    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.

  5. rivershah commented on May 4, 2025

    @rivershah

    Still there in:

    codex --version
    0.1.2504301751
    
  6. tralston commented on May 15, 2025

    @tralston

    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
    
  7. mdave752 commented on Jun 28, 2025

    @mdave752

    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()
  8. rivershah commented on Jun 29, 2025

    @rivershah

    How is this issue still open and not fixed?

  9. codex-maintainers commented on Aug 7, 2025

    @codex-maintainers

    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: #1174.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions