Repository navigation
server: OMB_HEADLESS_FULL_ACCESS, Full access over the API as an operator opt-in (#3) - #10
Conversation
The Organization Admin decided that Steward bot teams on a Lazurio Environment run without a sandbox: the Environment, one Machine with one operator, is the boundary. These tests pin the contract of OMB_HEADLESS_FULL_ACCESS before it exists. Without it, a bot PATCH to Full is refused with upstream's exact 403. With it, the loopback owner sets a Codex bot and a Claude bot to Full through that PATCH, the next turn runs Codex with never approvals in the danger-full-access sandbox and Claude with bypassPermissions and no approval broker, Custom stays refused, and an unknown value stops the server at start. The verification fixture passes the variable to its server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ator opt-in (#3) Upstream reserves Full and Custom for the packaged desktop app, because a headless server trusts every loopback request as its owner and any local process could otherwise raise a bot to Full. On a Lazurio Environment the Machine and its one operator are the boundary, and Steward bot teams run without a sandbox there (Organization Admin decision, plan DEV-6632). With OMB_HEADLESS_FULL_ACCESS=1 at server start, the owner on loopback may enter Full from Ask, Edits or Auto through the ordinary bot PATCH. Custom, leaving Custom, thread-level changes, paired sessions and service-trust loopback stay refused as upstream; so does everything in the desktop app, which keeps its own confirmation. Full then is upstream's Full: the drivers map it to Codex never approvals in the danger-full-access sandbox and to Claude bypassPermissions, and a Chief of Staff with Full passes it on to the work it delegates. The server logs one line at start when the opt-in is on and one line per bot raised to Full; an unknown value stops it at start. Unset, nothing changes. The logic lives in the new server/headless-full-access.ts; server/index.ts only calls it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Organization Admin decided on 2026-10-01 that Steward bot teams on a Lazurio Environment run without a sandbox, superseding the earlier decision for Auto: the Environment, one Machine with one operator, is the boundary. LAZURIO.md lists OMB_HEADLESS_FULL_ACCESS among the overlay and says what the operator does after the import: set each bot's engine, Codex or Claude, then Full, with the service started with the opt-in, which Machines never sets. docs/self-hosting.md documents the variable, how to set a bot to Full through the local API and exactly what is and is not unlocked. The intake's security notes and the release runbook (Machines contract, overlay table, what is deliberately not in the overlay) say the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Exact-head Steward QA: APPROVED at ed2c92e (base ca0e54f). I traced the sole server guard change: with the flag off the upstream 403 remains; with it on, only an owner-trust loopback bot PATCH entering Full from Ask/Edits/Auto passes. Custom, leaving Custom, paired sessions, service-trust loopback, thread-only routes, and model-facing tool files stay unchanged. Existing provider handling supplies Codex never + danger-full-access and Claude bypassPermissions. The seven modified upstream files are all on allowed_upstream_changes; Lazurio docs supersede the former Auto decision and explain the operator-only opt-in. Local isolated fixture: 53 targeted tests passed (including driver-wire and both engines), 334 related tests passed with one skipped, release contract 6/6, typecheck, lint and i18n passed. Current-head fork CI: overlay, archive and four Vitest shards succeeded; no unresolved threads or other pending reviewer. Non-blocking documentation follow-up: the upstream-generic docs/approval-levels.md still says Full can only be enabled in the packaged desktop app; the linked self-hosting page documents this explicitly scoped headless exception. No merge performed.
Status: ready for review
Why
The Organization Admin decided on 2026-10-01 (plan DEV-6632, issue #3) that Steward bot teams on a Lazurio Environment run without a sandbox: the Environment, one Machine with one operator, is the boundary. This supersedes the earlier decision that they run on Auto. Both engines must work: Codex CLI and Claude CLI, as the Environment's own signed-in CLIs.
Upstream lets Full (and Custom) be chosen only in the packaged desktop app, over a private process channel. Over HTTP the server answers
403 This approval-level change can only be made from the packaged desktop app, because a headless server trusts every loopback request as its owner and any local process could otherwise raise a bot to Full. On a headless Environment there is no desktop app, so a Steward team could never reach Full.What changes
OMB_HEADLESS_FULL_ACCESS, an operator-only opt-in read once at server start (newserver/headless-full-access.ts, styled likeOMB_DEFAULT_BOT_CWDandOMB_GITHUB_INTAKE):0,false: off, byte-identical upstream behaviour (403);1ortrue: on, and the server logs one line at start;OMB_DESKTOP_PARENT=1), which keeps its own confirmation, like upstream'sOMB_SHARED_WORKSPACE_FULL_ACCESS.PATCH /api/bots/:idlets the owner on loopback (the same trust the API already requires; not a paired session, notOMB_LOOPBACK_TRUST=service) enter Full from Ask, Edits or Auto. That is the only transition unlocked. The server logs one line per bot raised to Full.server/index.tschanges in three places only: the import and start log line, one extra condition on the existing 403, one log line after a successful raise.scripts/control-omb.tspasses the variable to the verification fixture.LAZURIO.md(overlay list; the Steward team paragraph now says Full without a sandbox, set engine first, then Full, the operator starts the service with the opt-in, Machines never sets it),docs/self-hosting.md(new "Full access without the desktop app" section with the variable, acurlexample and exactly what is unlocked),docs/lazurio-github-intake.md(Approval levels bullet),docs/operations/lazurio-fork-release.md(Machines unit contract, overlay table row, "Deliberately not in the overlay" now records the Auto to Full decision).What Full maps to (reused, not a parallel path)
The PATCH writes the same durable
approvalMode: "full"the desktop commit leaves behind; every turn then goes through upstream'sapprovalModeForTurn, so nothing new runs at turn time.server/drivers/codex.tsnamedApprovalParams("full")(lines 287-300): threadapprovalPolicy: "never",sandbox: "danger-full-access"; turnsandboxPolicy: { type: "dangerFullAccess" }.autoAcceptPermissions = approvalMode === "full"(line 1437) answers residual permission requests.server/drivers/claude.tslines 1224-1228 map Full to--permission-mode bypassPermissions, and lines 1413-1415 leave out--permission-prompt-toolfor it, so there is no approval broker.ask_userquestions still reach a person.delegationInheritsFullAccess(server/auto-approve.ts52-60, used inserver/index.tsdelegatedFullAccess): a Chief of Staff with Full makes the threads it delegates Full, if the recipient's engine implements Full. The Steward leader is a Chief, so its delegated work runs Full even if a worker were still on Ask.createTask, which copies the bot default), so it starts Full. A conversation that already has its own level keeps it, as upstream.What does not change
PATCH /api/bots/:id/tasks/:threadId),POST /api/bots/:id/taskswithfull, paired sessions, service-trust loopback: unchanged.server/drivers/agents-catalog-wire.test.tspasses unchanged.docs/approval-levels.mdis left unchanged to keep the upstream diff minimal; the opt-in is documented indocs/self-hosting.md, already on the allowlist.Security note
config.json, not settable by a bot through the API. Machines never sets it; the operator adds it to the unit of their own Environment.Verification
All run locally on this head:
v0.1.91, no merge commits, only allowlisted upstream files changed): passes. Touched upstream files:server/index.ts,scripts/control-omb.ts,docs/self-hosting.md, all already inallowed_upstream_changes(a comment now records this overlay against them).node --test scripts/lazurio-release-contract.node-test.mjs: 6/6 pass.pnpm typecheck: pass.pnpm lint: pass.pnpm i18n:check: pass.server/headless-full-access.test.ts(parser and permit matrix) andserver/headless-full-access.e2e.test.ts(real isolated server with fake Codex and Claude engines): off gives the exact upstream 403; on, a Codex bot and a Claude bot PATCH to Full with 200, Custom still 403 on Codex; the next Codex turn'sthread/start/turn/startcarrynever+danger-full-access/dangerFullAccess, the next Claude turn runs--permission-mode bypassPermissionswithout--permission-prompt-tool; back to Ask works; an unknown value stops the server at start with the reason; start and raise log lines present.auto-approve,full-access-workflows.e2e,default-bot-cwd(.e2e),github-intake(.e2e),lazurio-steward-team,hosted-access,enterprise,drivers/agents-catalog-wire,drivers/codex,drivers/claude(14 files, 446 passed, 1 skipped), plusserver/index.test.ts(260 passed).scripts/control-omb.tshas no unit test of its own; the e2e test exercises its fixture pass-through.Fork CI on this head: Archive linux-x64 passes. "Overlay, typecheck and lint" fails at its first step, "Upstream workflows stay disabled", because GitHub registered the upstream
ios-thread-ui.ymlas an active workflow on 2026-10-01; this PR does not cause it, and the vitest shards are skipped because they depend on that job. Fix it in repository settings, then re-run: #11.Follow-ups
curl.codexandclaudeCLIs; the tests use repository fakes. Claude Code is known to refuse skipping permissions when it runs as root, so the service should keep running as the operator's user, as the runbook's user unit does; not verified live here.🤖 Generated with Claude Code