Skip to content

Approval requests and sandbox policies

Thread-level policy and sandbox mode

ThreadConfig controls defaults persisted for subsequent turns on that thread.

from codex_app_server_sdk import ThreadConfig

cfg = ThreadConfig(
    approval_policy="on-request",
    sandbox="workspace-write",
)

approval_policy values:

  • untrusted
  • on-failure (deprecated by protocol, still accepted)
  • on-request
  • never

sandbox values:

  • read-only
  • workspace-write
  • danger-full-access

Turn-level sandbox policy

Use TurnOverrides.sandbox_policy for per-turn policy payloads.

from codex_app_server_sdk import TurnOverrides

turn = TurnOverrides(
    sandbox_policy={
        "type": "workspaceWrite",
        "networkAccess": False,
        "writableRoots": ["/tmp"],
    },
    approval_policy="on-request",
)

TurnOverrides.sandbox_policy accepts either:

  • typed SandboxPolicy structures, or
  • raw mapping payloads (for compatibility with server extensions).

Approval request handling

The client handles v2 server-initiated approval requests:

  • item/commandExecution/requestApproval
  • item/fileChange/requestApproval

If no handler is registered, the SDK auto-responds with decline (continue turn).

Callback mode

from codex_app_server_sdk import (
    CodexClient,
    CommandApprovalRequest,
    CommandApprovalWithExecpolicyAmendment,
    FileChangeApprovalRequest,
)


async def handler(req):
    if isinstance(req, CommandApprovalRequest):
        if req.reason and "network" in req.reason.lower():
            return "decline"
        return CommandApprovalWithExecpolicyAmendment(["uv", "run"])

    if isinstance(req, FileChangeApprovalRequest):
        return "accept_for_session"

    return "decline"


async with CodexClient.connect_stdio() as client:
    client.set_approval_handler(handler)
    result = await client.chat_once("Run a command that may request approval.")
    print(result.final_text)

Stream mode (manual response)

import asyncio
from contextlib import suppress
from codex_app_server_sdk import CodexClient, CommandApprovalRequest


async with CodexClient.connect_stdio() as client:
    async def approval_loop():
        async for req in client.approval_requests():
            if isinstance(req, CommandApprovalRequest):
                await client.approve_approval(req, for_session=True)
            else:
                await client.decline_approval(req)

    approval_task = asyncio.create_task(approval_loop())
    try:
        result = await client.chat_once("Run a task that may need approvals.")
        print(result.final_text)
    finally:
        approval_task.cancel()
        with suppress(asyncio.CancelledError):
            await approval_task

approval_requests() is observational. If a callback is configured, callback handling remains authoritative.