Provider: Codex CLI adapter behind AgentProvider — research-pinned spec (supersedes #2) #87
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#87
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Supersedes #2 (closed as superseded — its triage history remains the record for the attach decision and the #6 seam refactor). That issue predates the provider-seam hardening wave this one builds on: ADR-0033 (provider-declared attribution markers, #75), ADR-0034 (per-provider login sessions #77, generic per-provider host config #78), ADR-0035 (lab-owned context-file names + skills index for skills-less providers, #79), and #80 (two-tier conformance suite + provider-authoring checklist — implementing now, lands before this issue starts).
Definition of done = #80's bar: pass the Tier 1 hermetic conformance suite + run the Tier 2 live spikes, evidenced by a committed compat record, following the provider-authoring checklist.
Carried forward from #2's triage (still binding):
DeepLinker; instance rows show the copyable tmux-attach affordance.codex0.133.0, already in the host store).Decisions from the 2026-07-10 grill:
--permission-mode auto; isolation is the host's job (service user + worktree), not the agent sandbox's.-coverrides first; the global~/.codex/config.tomlis touched only for settings an override verifiably cannot carry.AuthFlowOAuthRedirect) with SSH port-forward instructions; API-key flow out of scope.notify/hooks findings are recorded in the compat record as the follow-up route.AGENTS.local.mdvia fallback config + a global$CODEX_HOME/AGENTS.mdbridge (see Seeding).Research basis: online docs sweep of 2026-07-10 (developers.openai.com/codex CLI/config/auth/hooks/sandbox references + upstream issues). Facts below marked (spike N) are unverified against the pinned binary and appear in the live-spike checklist.
Design
Spawn
--ask-for-approval neveris documented: no approval prompts ever.danger-full-accessavoids the two known workspace-write traps for lab sessions: network off by default (breakslabctl/git push) and writes confined to cwd (a linked worktree's commits write to the shared git dir outside cwd).gpt-5.5(default candidate),gpt-5.6-sol/gpt-5.6-terra/gpt-5.6-luna; do not pin slugs retiring 2026-07-23 (gpt-5.1-codex*,gpt-5.2-codex, …). Efforts:minimal|low|medium|high(+xhighwhere the model supports it (spike 2)), defaultmedium, applied via-c model_reasoning_effort=.SpawnOptions(): none declared in v1.Auth
AuthFlow{Kind: oauth-redirect};Instructionscarries the headless recipe (documented upstream):ssh -L 1455:localhost:1455 <host>, run login, open the printed URL in the local browser.LoginStartdrivescodex logininside the per-provider login session (ADR-0034 naming,tmuxx.IsLoginSession) and scrapes the authorize URL (spike 5: which stream it prints on);LoginSubmitCodereturns a typed not-supported error (redirect flows have no pasted code); completion is detected by forcedAuthStatuspolling overcodex login status(exit 0 = logged in; non-zero value (spike 5)).Logout=codex logout+ forced status refresh (seam contract: success decided from status, not exit code). Credentials live in$CODEX_HOME/auth.json(default~/.codex).Workspace seeding (SeedMeta + SeedWorkspace)
ContextFileName: "AGENTS.local.md"(#79 hard rule: lab-owned, never repo-tracked).project_doc_fallback_filenamesis fallback-only per directory level — a repo-committedAGENTS.mdat the worktree root would silently skip the fallback:-c project_doc_fallback_filenames=["AGENTS.local.md"](covers repos without their own AGENTS.md), and$CODEX_HOME/AGENTS.mdbridge whose sole content instructs: ifAGENTS.local.mdexists at the workspace root, read and follow it — the global file is always concatenated regardless of repo files, so lab context survives a tracked AGENTS.md.Both couplings live-verified end-to-end, including against a repo that tracks its own
AGENTS.md(spike 9).NativeSkillDiscovery: false→ the seeder appends the ADR-0035 skills index to the context file. Spike note: the TUI now has a/skillscommand — check whether 0.133+ has native skill discovery from a seedable path; if so, flip to native (spike 8b).ExcludeEntriescover every seeded path;SeededPathPatternsstay narrower (guard never rejects a repo's own tracked codex config).ScrubPatterns(BRE∩RE2 dialect, #75): defensive shapes for the documented attribution defaultCo-authored-by: Codex <noreply@openai.com>plus generated-with-Codex variants, shipped as the adapter's conformance marker samples; reconciled against Tier 2 ground truth (spike 10).-c commit_attribution=""in the spawn argv (upstream:commit_attributionconfig backs thecodex_git_commitfeature's co-author trailer).SeedWorkspacestays minimal (config rides argv). One candidate exception: directory trust. Whether-c 'projects."<worktree>".trust_level="trusted"'satisfies the first-run trust prompt is undocumented (spike 3); only if it fails doesSeedWorkspacefall back to a guarded append of the[projects]entry to~/.codex/config.toml.Chat surface
LocateTranscript: newest rollout under$CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonlwhosesession_meta.cwdmatches the worktree (cwd is recorded;codex resume --lastis cwd-scoped upstream).ReadTranscript: map the rollout JSONL (session_meta;response_itemsubtypesmessage/function_call/function_call_output/reasoning;event_msgtask_started/task_complete/agent_message/token_count;turn_context) into the universal schema — reasoning → text+Thinking, function_call(+output) → tool chips, task events → lifecycle/state./newcarriesCommandRoleClear. Whether/newopens a new rollout file (identity rotates for free) or clears in place (adapter synthesizes an epoch-qualified path) is (spike 4).Reply: tmux send-keys. Upstream: Enter mid-turn steers the current turn (input is injected, not dropped) — reply-while-working is legal with no typed drop error. Multi-line delivery (bracketed paste vs Ctrl+J newlines) (spike 7).Interrupt: single Esc. Ctrl-C is a quit hazard (conflicting upstream sources; some versions exit immediately) — the recipe must never emit it; double-Esc triggers backtrack/fork on an empty composer, hazard-check both (spike 6).Commands: curated catalog;/new(role clear, ChatSafe), picker-opening commands (/resume,/fork,/model,/permissions, …) shipChatSafe: false. Note upstream rename/approvals→/permissions.Answerable: false+ tmux-attach hint.DeepLinker(no web surface),LiveSignals(no verified structured channel in v1; compat record documentsnotify=agent-turn-completeonly, and the 2026 hooks system incl.PermissionRequest(spike 8) as the follow-up route),ConnectingReporter(nothing to connect).Plan (phased, one issue)
internal/provider/codexagainst the pinned couplings; register the provider; keep core grep-clean.Live-spike checklist (each answer → compat record)
/status,/model).xhigheffort support per model; the rumored-m model[effort]bracket syntax — real?-c 'projects."<path>".trust_level="trusted"'as trust pre-grant — works? (else SeedWorkspace config-append fallback)/new: new rollout file (rotation) vs in-place clear (epoch synthesis required).codex login statusexit codes, tmux login-screen Ctrl-C bug (#23820) workaround.PermissionRequestet al.) — record only, for the LiveSignals follow-up. 8b:/skills— native skill discovery? From a seedable path?project_doc_fallback_filenamespresent + fallback semantics on 0.133; global$CODEX_HOME/AGENTS.mdbridge verified against a repo WITH a trackedAGENTS.md.-c commit_attribution=""verified; ScrubPatterns reconciled.tmux-256colorvsxterm-256color; OSC 11 timeout issue #22761).Acceptance
provider: codexruns manual + AFK sessions end to end (seed prompt as trailing argv positional; tracker-agnosticlabctlseed vocabulary unchanged).commit_attribution=""), the adapter's ScrubPatterns strip its own marker samples through the union path, pre-push guard clean.internal/provider/codex(grep-clean).Out of scope
AuthFlowAPIKeyhas no mechanism behind it; separate issue if wanted).DeepLinkerwork.Sequencing
needs-triageuntil #80 merges — its suite + checklist are this issue's definition of done, so it starts after. At re-triage, expectedready-for-human: every fragile coupling needs a live logged-in codex session, which an AFK agent on this host cannot drive.Amendment: auth flow → device-code (maintainer direction, 2026-07-10)
Decision 4 changes: device-code authorization replaces the ChatGPT OAuth localhost redirect. Lab normally runs on a server; the redirect flow needs an SSH port-forward to localhost:1455, while device auth needs only a browser anywhere and a one-time code. Verified live on the pinned binary (codex-cli 0.133.0, scratch
CODEX_HOME):codex login --device-authexists and prints, on stdout: the URLhttps://auth.openai.com/codex/deviceand a one-time code (XXXX-XXXXX, 15-minute expiry).codex login statuswhile logged out:Not logged in, exit 1 (spike 5's non-zero value, now pinned).Spec deltas:
AuthFlowpresents the device-auth recipe; the SSH port-forward instructions are dropped.LoginStartdrivescodex login --device-authin the ADR-0034 login session and scrapes URL + one-time code from stdout (stream verified).LoginSubmitCodestays a typed not-supported error — the code is entered browser-side at the printed URL, never pasted into the CLI.codex login status(exit 1 = logged out confirmed; exit 0 output shape still to pin once logged in).statusoutput/exit, and the tmux Ctrl-C login-screen hazard (openai/codex#23820) under the device flow.Host ground truth (2026-07-10): codex on this host is authenticated only for the human operator's account, not the lab service user (
/var/lib/lab/.codexhas noauth.json). Tier-2 spikes requiring auth need a lab-user login first — the device flow above is the recipe.Amendment 2: Tier-2 spike results — all 11 live-spike questions answered (2026-07-10)
Codex is now authenticated for the lab service user (
auth.jsonin$CODEX_HOME;codex login status→Logged in using ChatGPT, exit 0 — spike 5's remaining shape, now pinned). All checklist items were run live against the pinned binary (codex-cli 0.133.0) on this host, headless viacodex execplus a real TUI driven over tmux. Answers below are ground truth for the compat record; each supersedes the corresponding (spike N) marker in the spec.Spike 1–2 — models & efforts
gpt-5.5(default) andgpt-5.4-mini, both with efforts low / medium (default) / high / xhigh. Nominimaltier.codex debug modelsrenders the catalog as machine-readable JSON (slugs, display names, default + supported reasoning levels) — pin the adapter's catalog from this, not TUI scraping. It also lists internalcodex-auto-review(visibility-filter before surfacing).gpt-5.6-terra/gpt-5.6-lunaexist server-side but are rejected with "requires a newer version of Codex" on 0.133;gpt-5.6-solandgpt-5.1-codexare rejected outright. An invalid model fails at request time with an API 400 — there is no client-side catalog validation.-m model[effort]bracket syntax is refuted: the string is passed verbatim as the slug and 400s. Effort rides-c model_reasoning_effort=(verifiedhighandxhighend-to-end; the exec banner echoes the resolved effort).codex execdefaults toreasoning effort: nonein its banner; the TUI defaults to medium. Spawns should pass the effort explicitly.Spike 3 — trust pre-grant
-c 'projects."<path>".trust_level="trusted"'does not suppress the first-run trust prompt — the prompt appears even with explicit--ask-for-approval never --sandbox workspace-write.[projects."<path>"]\ntrust_level = "trusted"to~/.codex/config.tomlsuppresses the prompt.SeedWorkspacemust do the guarded config append; the argv route is dead for trust.codex execruns in them — the adapter's config guard must tolerate codex mutatingconfig.tomlon its own.Spike 4 —
/newRotates.
/newopens a fresh rollout JSONL with a new session id (old file untouched, TUI prints acodex resume <old-id>hint). Clear/epoch identity comes for free; no synthesized epoch path needed. Locate-by-newest-cwd-match works (both files carry the cwd insession_meta). Note: the filename timestamp is local time; payload timestamps are UTC.Spike 5 — login (device flow re-verified)
codex login --device-authre-verified against the real$CODEX_HOME: URL + one-time code print on stdout.Not logged in/ exit 1; logged inLogged in using ChatGPT/ exit 0.Spike 6 — interrupt hazards
esc to interrupt. In the rollout this lands asevent_msg/turn_abortedwithreason:"interrupted"— a clean lifecycle marker for the reader.qexits back to the composer. The recipe must not emit Esc when idle (interrupt is only legal while working), andqis the recovery if the overlay is ever entered.Spike 7 — reply injection
paste-buffer -p) lands multi-line text in the composer without submitting; Enter then submits it as one message. Ctrl+J also inserts a literal newline. Both recipes verified.send-keyscalls with a delay — sent in one burst, the command text merges with subsequent input instead of executing.Spike 8 / 8b — hooks & skills
codex features:hooks stable true, plusplugin_hooks). Event vocabulary present in the binary:SessionStart,UserPromptSubmit,PreToolUse,PostToolUse,Stop,SubagentStop,PreCompact,PermissionRequest, withhook_event_name/stop_hook_activepayload keys. The LiveSignals follow-up has a real substrate; legacynotify(agent-turn-complete) also still present./skills: native skills exist (SKILL.md format,$CODEX_HOME/skills/, system skills preinstalled) — but discovery is global-only. Planted marker skills in<repo>/.codex/skills/and<repo>/.agents/skills/were not discovered; only the$CODEX_HOME/skills/marker was. No per-workspace seedable path ⇒NativeSkillDiscovery: falsestands; the ADR-0035 context-file skills index stays.Spike 9 — context-file wiring (all three legs verified)
AGENTS.md:-c 'project_doc_fallback_filenames=["AGENTS.local.md"]'→ content read (marker surfaced). The key exists and works on 0.133.AGENTS.md: fallback silently skipped, exactly the trap the spec predicted — only the tracked file's marker surfaced.$CODEX_HOME/AGENTS.mdbridge ("if AGENTS.local.md exists at the workspace root, read and follow it"): codex readAGENTS.local.mdat runtime via a tool call and followed it, trackedAGENTS.mdnotwithstanding. The double-path design is confirmed end-to-end.Spike 10 — attribution
Co-authored-bytrailer, no attribution of any kind — the commit message is verbatim. Thecodex_git_commitfeature isremoved(off) in 0.133.commit_attributionis an unknown config field on 0.133: silently ignored normally, hard error under--strict-config. Spec delta: drop-c commit_attribution=""from the spawn argv. ScrubPatterns stay as defensive conformance samples only.Spike 11 — TERM / terminal
default-terminalistmux-256color; codex TUI renders and runs fine under it across many starts — no OSC 11 hang observed.codex doctorflags TERMINFO unreadable under the nix profile paths (cosmetic here; worth a compat-record note). tmuxextended-keysis off on this host.New findings outside the checklist
.gitread-only — git commits are impossible in codex's workspace-write sandbox even with.gitinside cwd (a third trap alongside network-off and cwd-confined writes; escape hatch-c 'sandbox_workspace_write.writable_roots=[...]'exists). The full-access, never-ask posture (decision 2) is reconfirmed with stronger evidence.codex execannounces "Reading additional input from stdin..." when stdin is a pipe — spawn recipes should keep the prompt as the argv positional and not leave a piped stdin dangling.codex doctorgives a one-shot diagnostic (auth, state DB integrity, terminal) — useful in the compat record and for support tooling.Spec deltas from this round
-c commit_attribution=""(unknown field; attribution is absent at source in 0.133).SeedWorkspace: trust must be granted via guardedconfig.tomlappend (the-croute is confirmed dead) — the spec's fallback becomes the primary.codex debug models; v1 catalog =gpt-5.5(default, effort default medium) +gpt-5.4-mini, efforts low/medium/high/xhigh; always pass effort explicitly on spawn.qrecovers); slash commands typed and submitted as separate keystroke bursts./new= file rotation (no epoch synthesis); mapturn_aborted(reason=interrupted)to the interrupted lifecycle state; filename timestamps are local, payload UTC.Sequencing note: #80 (conformance suite + checklist, this issue's definition-of-done bar) has merged, and the host now has an authenticated codex for the lab user — both former blockers are gone. The original
ready-for-humanexpectation rested on "an AFK agent cannot drive a live logged-in codex session"; this triage session just drove all of the fragile couplings from exactly that seat, so that rationale is obsolete. Re-triage decision (state label) pending maintainer direction.Agent Brief
Category: enhancement
Summary: Implement the Codex CLI adapter behind
AgentProvider, to #80's conformance bar, per the pinned spec in this issue.This brief is a pointer-contract: the issue body (research-pinned spec) + Amendment 1 (device-auth) + Amendment 2 (Tier-2 spike results) together are the authoritative specification. Every (spike N) marker in the body is superseded by Amendment 2's ground truth; where body and amendments conflict, the amendments win. Key deltas the implementer must honor:
-c commit_attribution=""(unknown field on 0.133; attribution is absent at source).codex login --device-auth, URL + one-time code scraped from stdout;LoginSubmitCodereturns typed not-supported; status shapes: exit 1 "Not logged in" / exit 0 "Logged in using ChatGPT").-cis dead:SeedWorkspacedoes a guarded[projects."<worktree>"] trust_level = "trusted"append to~/.codex/config.toml, tolerant of codex's own auto-written entries.codex debug models:gpt-5.5(default; effort default medium) +gpt-5.4-mini, efforts low/medium/high/xhigh; effort always passed explicitly via-c model_reasoning_effort=; no bracket syntax./newrotates the rollout file (new session id) — no epoch synthesis; mapturn_aborted(reason="interrupted")to the interrupted lifecycle state; rollout filename timestamps are local time, payload timestamps UTC.qrecovers); slash command text and its Enter are separate paced key bursts; multi-line replies via bracketed paste (or Ctrl+J newlines); mid-turn replies are queued-after-next-tool-call — legal, no drop error.AGENTS.local.mdvia-c 'project_doc_fallback_filenames=["AGENTS.local.md"]'plus the lab-managed global$CODEX_HOME/AGENTS.mdbridge (both legs live-verified, including against a repo with a trackedAGENTS.md).NativeSkillDiscovery: false(0.133 discovers skills only from global$CODEX_HOME/skills/, no per-workspace path) → seeder appends the ADR-0035 skills index.DeepLinker,LiveSignals(hooks are stable in 0.133 with SessionStart/PreToolUse/PostToolUse/Stop/etc. — record in compat.md as the follow-up route),ConnectingReporter.Acceptance criteria (= issue body's Acceptance section, restated):
provider: codexruns manual + AFK sessions end to end (prompt as trailing argv positional; stdin not left as a dangling pipe).Out of scope: unchanged from the issue body (LiveSignals implementation, API-key auth, DeepLinker/remote interface, #74 binary deployment, #80 suite itself).
Environment note for the implementing agent: codex 0.133.0 is on PATH and authenticated for the lab user; live re-verification runs are possible from the lab seat (drive the TUI via tmux; keep prompts trivial; codex's workspace-write sandbox blocks git writes — see Amendment 2).
Landing audit — PR #91 (
afk/87): PASSVerification signal relied on: Forgejo Actions CI —
ci / nativesuccess (run #96, 4m45s). Per this repo's gate model I relied on that green rather than re-runninggo test/lint; the compat record notes its ownLAB_COMPAT_LIVE=1probes passed against the real binary on the build host.Checks:
feat(provider): …).afk/87, body carries a workingCloses #87. ✓main— no conflicts (merge-tree clean).AuthFlowDeviceCode,ErrLoginCodeUnsupported, and the optionalLoginCodeReportercapability (advertised by type-assertion likeConnectingReporter/DeepLinker); httpapi echoesuser_codeand maps the sentinel to 409 with no concrete-provider coupling; the SPA auth card gains a descriptor-drivendevice-codebranch. Existing adapters/fakes untouched.codexadapter is package-isolated —TestCoreAttributionNeutralityexempts it with the same rationale asclaudecode; no codex coupling outside the adapter + composition root.--ask-for-approval never --sandbox danger-full-access,-cvalues as single argv elements).Intentional deviations from this issue's original spec — both documented in the PR body as evidence-based/maintainer amendments, not defects:
oauth-redirect(maintainer amendment).-c commit_attribution=""— unknown config key on codex 0.133 (hard-errors under--strict-config); attribution is absent at source anyway.Verdict: PASS. Awaiting explicit free-text merge go-ahead before merging.