Skip to content
Changelog

Approve Claude and Codex permission requests in the AgentOS chat

October 29, 20263 min read

A Claude or Codex agent behind AgentOS could ask for permission with nobody there to answer. Agno now turns each native request from ClaudeAgent and CodexAgent into a PAUSED run with a confirmation card in the chat. The agent only needs a database, and AgentOS(db=...) can supply it. The harness's own policy decides when to ask.

Check your CodexAgent settings

This upgrade changes what a CodexAgent with a database and no approval settings does. Earlier releases passed its escalated requests to Codex's automatic reviewer, the SDK default. Agno now leaves the policy to Codex's own configuration and sends each review to a person. A fresh Codex configuration reports on-request, so those requests pause the run, and a Codex configuration set to never keeps them from pausing. hitl_timeout bounds the wait at 900 seconds by default. When it runs out, the run ends in ERROR and the action stays unexecuted.

from agno.agents.codex import CodexAgent
 
# Set the policy yourself, and how long a request waits (seconds)
CodexAgent(id="codex", approval_policy="on-request", hitl_timeout=3600)
 
# Opt out: Codex refuses anything that needs approval
CodexAgent(id="codex", approval_policy="never")
 
# Opt out: keep Codex's automatic reviewer, as in earlier releases
CodexAgent(id="codex", approval_mode="auto_review")

Pausing Codex requests needs openai-codex==0.163.0, and sandbox images need a rebuild alongside the upgrade, since a runtime without this support refuses to start.

ClaudeAgent with a database pauses in the same way, including when permission_mode is unset. bypassPermissions and dontAsk keep their native behavior, and so does your own can_use_tool callback or permission_prompt_tool_name.

Confirm or reject the request

The AgentOS chat shows the card. An API client sends the paused tool back with confirmed set, and AgentOS delivers that answer to the process still waiting on it.

tool = run.requirements[0].tool_execution
tool.confirmed = True  # False rejects
httpx.post(
    f"{os_url}/agents/codex/runs/{run.run_id}/continue",
    data={"session_id": run.session_id, "stream": "true", "tools": json.dumps([tool.to_dict()])},
)

We ran this through AgentOS on both releases with the real CodexAgent and Codex binary, and again with ClaudeAgent and its bundled CLI. A local stub server stands in for the model and scripts one shell command that needs approval, because no model key was available. The command writes a marker file.

Real output of the flow in four steps. Codex asks to run a command. AgentOS stores the run as PAUSED with the marker file unwritten. A POST to the continue endpoint sends confirmed=True. The waiting process carries on to COMPLETED with the marker written and two model requests in total.

# Before
POST /agents/codex/runs -> 200 ['RunStarted', 'RunCompleted']
# Now
POST /agents/codex/runs -> 200 ['RunStarted', 'RunPaused']
POST /agents/codex/runs/<id>/continue confirmed=True -> 200 ['RunContinued', 'RunCompleted']

In the Before run nobody got asked. Codex's automatic reviewer rejected the command after the stub returned no valid assessment, and Claude's CLI told the model the command needed approval. In the Now run, a single thread/start and turn/start cover the whole request, and the marker appears only after the confirm. A reject leaves the marker unwritten and tells the model the request was declined.

What AgentOS refuses

  • Continuing without an answer returns 409 Confirm or reject the pending 'commandExecution' request to continue.
  • Editing the arguments returns 409 Continuation does not match the reviewed native action.
  • Repeating a finished continue returns 400, and nothing runs again.
  • Cancelling a paused run leaves it CANCELLED with the action unexecuted.

A nonstreaming submission to these agents now returns 202 with a run_id, since AgentOS runs them in the background so a wait can outlive the browser connection. acontinue_run and the sync continue_run both deliver the answer. Claude's AskUserQuestion and Codex's request_user_input pause too, as user feedback in the chat.

Sandboxes and lost workers

Agents on DockerSandbox and AgentCore wait inside their container, and the coordinator delivers the answer there, so another API replica can take over when the first one dies. Without a sandbox the process waits in the API worker that started the run, and losing that worker fails the run with no replay. These paths rest on the released source and its tests, since the run above used no container.

Permission requests support confirm and reject only, with no standing grants. Team members and workflow steps fail on an actual permission request before the action runs.

See the cookbook, and learn more about user confirmation in the documentation.

Frequently asked questions

Pass approval_policy="never" and Codex refuses any request that needs approval. Pass approval_mode="auto_review" to keep Codex's automatic reviewer, the default in earlier releases. An agent run outside AgentOS with no database also never pauses.

After hitl_timeout seconds, 900 by default, the run ends in ERROR with Native request expired before it was answered. The action stays unexecuted, and a late continue returns 400.

No. Each one pauses the run as an ordinary confirmation tool, answered in the chat by the person running it. GET /approvals returned a total_count of 0 while a test run sat paused, and the tool carries approval_id=None.

With a stub server as the model: wire thread/start approvalPolicy= None approvalsReviewer= user, codex reports effective approvalPolicy= on-request approvalsReviewer= user, codex asks: item/commandExecution/requestApproval, POST /agents/codex/runs -> 200 [RunStarted, RunPaused], run status: PAUSED | marker exists: False | model requests: 1, GET /approvals -> 200 total_count 0, continue with no answer -> 409, POST /agents/codex/runs/<id>/continue confirmed=True -> 200 [RunContinued, RunCompleted], run status: COMPLETED | content: Finished after the shell call. | marker exists: True.

Shipped around the same time