Containerized provider login flow #206

Closed
opened 2026-07-22 11:44:25 +02:00 by dominik.polakovics · 3 comments

What to build

Run claude auth login / codex login sessions inside a container instead of on the host, using the same runner machinery as #205 but with the master credential store mounted rw (instead of a per-run copy) so completed logins land in the master store that spawns copy from (#202). OAuth URL scrape via CapturePane keeps working through the tmux pane. With this, hosts no longer need the agent CLIs installed at all — the tools image is the only CLI distribution.

Acceptance criteria

  • Login sessions for claude and codex run containerized (tools-image CLI, master store mounted) and persist working credentials to the master store
  • OAuth URL capture and login-completion detection work unchanged from the web UI
  • Subsequent container-mode runs authenticate using credentials produced by a containerized login
  • Host no longer requires provider CLIs on PATH for any flow when running container mode (docs/ops.md updated)
  • The login container resolves the master store through lab-side resolution (CODEX_HOME/CLAUDE_CONFIG_DIR honored for the mount source, config-dir var pinned explicitly inside the container) — the operator override works in container mode by construction (#211)

Blocked by

## What to build Run `claude auth login` / `codex login` sessions inside a container instead of on the host, using the same runner machinery as #205 but with the master credential store mounted rw (instead of a per-run copy) so completed logins land in the master store that spawns copy from (#202). OAuth URL scrape via CapturePane keeps working through the tmux pane. With this, hosts no longer need the agent CLIs installed at all — the tools image is the only CLI distribution. ## Acceptance criteria - [ ] Login sessions for claude and codex run containerized (tools-image CLI, master store mounted) and persist working credentials to the master store - [ ] OAuth URL capture and login-completion detection work unchanged from the web UI - [ ] Subsequent container-mode runs authenticate using credentials produced by a containerized login - [ ] Host no longer requires provider CLIs on PATH for any flow when running container mode (docs/ops.md updated) - [ ] The login container resolves the master store through lab-side resolution (`CODEX_HOME`/`CLAUDE_CONFIG_DIR` honored for the mount source, config-dir var pinned explicitly inside the container) — the operator override works in container mode by construction (#211) ## Blocked by - #205
Author
Owner

This was generated by AI during triage.

Human Brief

Category: enhancement
Summary: Run claude auth login / codex login inside a container via the #205 runner with the master credential store mounted rw, so hosts no longer need provider CLIs installed.

The issue body's "What to build" and acceptance-criteria checklist are the authoritative spec (grilled 2026-07-22).

Why ready-for-human rather than ready-for-agent:
Implementation is mostly reuse of the #205 runner machinery (same podman spawn, master store mounted rw instead of the #202 per-run copy; OAuth URL scrape via the existing tmux CapturePane path). But every acceptance criterion is verified by completing a real provider OAuth login in a browser against a podman-spawned session — and the lab agent sandbox can run neither podman nor a browser. An AFK agent would ship this completely blind, and a wrong mount or env would only surface as a broken login for a human later. The implement-and-verify loop belongs to a human on the dev host.

Key interfaces (for whoever picks it up):

  • The login-session spawn path (distinct from run spawn) — switch its command construction to the container runner seam from #205.
  • Mount difference vs #205: master credential store rw, no per-run instance HOME copy; completed logins must land where the #202 credentialInjector copies from.
  • OAuth URL capture + login-completion detection via the tmux pane — behavior must be unchanged from the web UI's perspective.
  • docs/ops.md: hosts running container mode no longer need provider CLIs on PATH.

Acceptance criteria: the checklist in the issue body.

Out of scope:

  • Changing the master store format or the OAuth capture mechanism.
  • Retiring host-mode login (host mode remains break-glass and keeps working).

Blocked by

(recorded in the issue body: #205)

> *This was generated by AI during triage.* ## Human Brief **Category:** enhancement **Summary:** Run `claude auth login` / `codex login` inside a container via the #205 runner with the master credential store mounted rw, so hosts no longer need provider CLIs installed. The issue body's "What to build" and acceptance-criteria checklist are the authoritative spec (grilled 2026-07-22). **Why ready-for-human rather than ready-for-agent:** Implementation is mostly reuse of the #205 runner machinery (same podman spawn, master store mounted rw instead of the #202 per-run copy; OAuth URL scrape via the existing tmux CapturePane path). But every acceptance criterion is verified by *completing a real provider OAuth login in a browser* against a podman-spawned session — and the lab agent sandbox can run neither podman nor a browser. An AFK agent would ship this completely blind, and a wrong mount or env would only surface as a broken login for a human later. The implement-and-verify loop belongs to a human on the dev host. **Key interfaces (for whoever picks it up):** - The login-session spawn path (distinct from run spawn) — switch its command construction to the container runner seam from #205. - Mount difference vs #205: master credential store rw, no per-run instance HOME copy; completed logins must land where the #202 credentialInjector copies from. - OAuth URL capture + login-completion detection via the tmux pane — behavior must be unchanged from the web UI's perspective. - docs/ops.md: hosts running container mode no longer need provider CLIs on PATH. **Acceptance criteria:** the checklist in the issue body. **Out of scope:** - Changing the master store format or the OAuth capture mechanism. - Retiring host-mode login (host mode remains break-glass and keeps working). ## Blocked by (recorded in the issue body: #205)
Author
Owner

This was generated by AI during triage.

Added an acceptance criterion (last checkbox): the login container must resolve the master store through lab-side resolution — mount source from the override-aware resolvers, config-dir var pinned explicitly inside the container. This makes the CODEX_HOME/CLAUDE_CONFIG_DIR operator override officially supported in container mode, and is the reason #211 was closed as wontfix (the remaining host-mode-login gap is declared unsupported).

> *This was generated by AI during triage.* Added an acceptance criterion (last checkbox): the login container must resolve the master store through lab-side resolution — mount source from the override-aware resolvers, config-dir var pinned explicitly inside the container. This makes the `CODEX_HOME`/`CLAUDE_CONFIG_DIR` operator override officially supported in container mode, and is the reason #211 was closed as wontfix (the remaining host-mode-login gap is declared unsupported).
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: Run provider login sessions inside a container via the #205 runner machinery, with the master credential store mounted rw, so container-mode hosts need no provider CLIs installed.

This brief supersedes the earlier Human Brief (2026-07-22): #205 has landed since, and the open design decisions were resolved in a grilling session with the maintainer on 2026-07-25. The issue body's acceptance-criteria checklist remains the authoritative contract; this brief pins the design. Human verification of the real OAuth flows happens after the PR merges — the agent ships against unit/integration tests only (see Verification).

Current behavior:
Both provider adapters spawn their login flow (LoginStart) as a host tmux session running the CLI from the host PATH, with no extra env. The OAuth/device URL is scraped from the pane via CapturePane polling; claude's pasted code is delivered with SendKeys; completion is detected lab-side by polling AuthStatus against the master store on the host filesystem. Container-mode hosts therefore still need every provider CLI installed just for login.

Desired behavior:
When container wiring is configured, a login session's pane command becomes a podman run invocation (tmux stays host-side, exactly the #205 tracer-bullet split). The CLI comes from the provider's agent-tools image mount; the master credential store is bind-mounted rw so a completed login lands in the same host directory the existing completion poller and credential injector already read. URL scrape, code paste, completion detection, and idempotent re-join of a live attempt behave identically from the web UI's perspective.

Design pins (grilled 2026-07-25):

  • Trigger — config-driven, no fallback. Login runs containerized whenever container wiring is configured (podman binary + preflight wired + tools image for that provider). A red or unfinished preflight makes LoginStart refuse with the actionable preflight error, mirroring the run-spawn refusals. Host-mode login remains only on servers with no container config at all — never as a silent fallback.
  • Image — the global default dev image (the server-wide --container-image). No per-repo resolution (login is repo-less). If no global default is set, refuse with an error naming the knob. Per-attempt EnsureImage applies, like runs.
  • Seam — a decorated SessionRunner. The wiring layer hands each adapter a SessionRunner that, for the login session, wraps the provider's CLI argv into the podman run pane command. The per-provider inputs (master-store dir, config-dir env var name) come from a small login-container spec the provider declares — podman knowledge stays out of the provider packages, and the adapters' login flows (scrape/paste/poll/teardown logic) remain untouched. The decorator's Stop also runs the RemoveContainer backstop (a login CLI may ignore SIGHUP).
  • Mounts/env. Master store rw, resolved through the lab-side override-aware resolvers ($CODEX_HOME / $CLAUDE_CONFIG_DIR honored for the mount source — the #211 criterion), mounted under the container-side home with the config-dir var pinned explicitly to the mount point. HOME is a per-attempt host scratch directory bind-mounted at the container-side home (ownership just works under --userns=keep-id), with the master-store bind nested inside; teardown wipes the scratch dir. Tools image mounted with the PATH prepend as in runs. No secrets in env; TERM forwards by name.
  • Limits/gates — reuse the global container settings (memory/pids/nofile rows; no repo override) and the same startup preflight verdict. No new knobs.
  • Lifecycle. Container name is the deterministic name derived from the login session name (labrun- namespace). The startup orphan sweep already accounts for live login sessions by construction, so a pending login's container survives a server restart and leaked ones are reaped — no sweep changes.

Key interfaces:

  • tmuxx.SessionRunner — the seam the decorator implements; adapters keep calling Start/Stop/CapturePane/SendKeys/IsRunning unchanged.
  • The provider contract — grows a small declaration of the login-container inputs (master-store dir resolution, config-dir var name); both adapters already own override-aware resolvers for these.
  • podmanx.RunSpec/RunArgv — reuse for rendering; a login spec omits worktree/bare/runtime/agent-sock mounts entirely.
  • The preflight/refusal surface — login refusals should carry the same actionable operator text as run-spawn refusals.

Acceptance criteria: the issue body's checklist, plus:

  • Unit tests cover the login RunSpec construction exhaustively: mount set (master store rw, scratch HOME, tools image, nothing else), env pins (config-dir var, PATH, TERM forward), override-aware mount source, deterministic name, refusals (red preflight, missing tools image, missing global dev image)
  • Decorator tests: non-login sessions pass through untouched; Stop runs the container backstop; a live containerized login attempt re-joins idempotently
  • A podman-gated integration test skips gracefully where rootless podman is unavailable
  • Host-mode login on a server without container config is byte-for-byte unchanged

Out of scope:

  • Retiring host-mode login (break-glass, stays working).
  • Changing the master store format, the OAuth capture mechanism, or any web-UI login surface.
  • A dedicated login image or login-specific resource limits (explicitly decided against).
  • Real-OAuth verification — the maintainer runs the manual matrix (claude login, codex device login, restart-mid-login resume, subsequent container run authenticating, CLI-less host) after the PR merges.
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** Run provider login sessions inside a container via the #205 runner machinery, with the master credential store mounted rw, so container-mode hosts need no provider CLIs installed. This brief supersedes the earlier Human Brief (2026-07-22): #205 has landed since, and the open design decisions were resolved in a grilling session with the maintainer on 2026-07-25. The issue body's acceptance-criteria checklist remains the authoritative contract; this brief pins the design. **Human verification of the real OAuth flows happens after the PR merges** — the agent ships against unit/integration tests only (see Verification). **Current behavior:** Both provider adapters spawn their login flow (`LoginStart`) as a host tmux session running the CLI from the host PATH, with no extra env. The OAuth/device URL is scraped from the pane via `CapturePane` polling; claude's pasted code is delivered with `SendKeys`; completion is detected lab-side by polling `AuthStatus` against the master store on the host filesystem. Container-mode hosts therefore still need every provider CLI installed just for login. **Desired behavior:** When container wiring is configured, a login session's pane command becomes a `podman run` invocation (tmux stays host-side, exactly the #205 tracer-bullet split). The CLI comes from the provider's agent-tools image mount; the master credential store is bind-mounted rw so a completed login lands in the same host directory the existing completion poller and credential injector already read. URL scrape, code paste, completion detection, and idempotent re-join of a live attempt behave identically from the web UI's perspective. **Design pins (grilled 2026-07-25):** - **Trigger — config-driven, no fallback.** Login runs containerized whenever container wiring is configured (podman binary + preflight wired + tools image for that provider). A red or unfinished preflight makes `LoginStart` refuse with the actionable preflight error, mirroring the run-spawn refusals. Host-mode login remains only on servers with no container config at all — never as a silent fallback. - **Image — the global default dev image** (the server-wide `--container-image`). No per-repo resolution (login is repo-less). If no global default is set, refuse with an error naming the knob. Per-attempt `EnsureImage` applies, like runs. - **Seam — a decorated SessionRunner.** The wiring layer hands each adapter a SessionRunner that, for the login session, wraps the provider's CLI argv into the `podman run` pane command. The per-provider inputs (master-store dir, config-dir env var name) come from a small login-container spec the provider declares — podman knowledge stays out of the provider packages, and the adapters' login flows (scrape/paste/poll/teardown logic) remain untouched. The decorator's `Stop` also runs the `RemoveContainer` backstop (a login CLI may ignore SIGHUP). - **Mounts/env.** Master store rw, resolved through the lab-side override-aware resolvers (`$CODEX_HOME` / `$CLAUDE_CONFIG_DIR` honored for the mount *source* — the #211 criterion), mounted under the container-side home with the config-dir var pinned explicitly to the mount point. HOME is a per-attempt host scratch directory bind-mounted at the container-side home (ownership just works under `--userns=keep-id`), with the master-store bind nested inside; teardown wipes the scratch dir. Tools image mounted with the PATH prepend as in runs. No secrets in env; TERM forwards by name. - **Limits/gates — reuse the global container settings** (memory/pids/nofile rows; no repo override) and the same startup preflight verdict. No new knobs. - **Lifecycle.** Container name is the deterministic name derived from the login session name (`labrun-` namespace). The startup orphan sweep already accounts for live login sessions by construction, so a pending login's container survives a server restart and leaked ones are reaped — no sweep changes. **Key interfaces:** - `tmuxx.SessionRunner` — the seam the decorator implements; adapters keep calling `Start`/`Stop`/`CapturePane`/`SendKeys`/`IsRunning` unchanged. - The provider contract — grows a small declaration of the login-container inputs (master-store dir resolution, config-dir var name); both adapters already own override-aware resolvers for these. - `podmanx.RunSpec`/`RunArgv` — reuse for rendering; a login spec omits worktree/bare/runtime/agent-sock mounts entirely. - The preflight/refusal surface — login refusals should carry the same actionable operator text as run-spawn refusals. **Acceptance criteria:** the issue body's checklist, plus: - [ ] Unit tests cover the login RunSpec construction exhaustively: mount set (master store rw, scratch HOME, tools image, nothing else), env pins (config-dir var, PATH, TERM forward), override-aware mount source, deterministic name, refusals (red preflight, missing tools image, missing global dev image) - [ ] Decorator tests: non-login sessions pass through untouched; Stop runs the container backstop; a live containerized login attempt re-joins idempotently - [ ] A podman-gated integration test skips gracefully where rootless podman is unavailable - [ ] Host-mode login on a server without container config is byte-for-byte unchanged **Out of scope:** - Retiring host-mode login (break-glass, stays working). - Changing the master store format, the OAuth capture mechanism, or any web-UI login surface. - A dedicated login image or login-specific resource limits (explicitly decided against). - Real-OAuth verification — the maintainer runs the manual matrix (claude login, codex device login, restart-mid-login resume, subsequent container run authenticating, CLI-less host) after the PR merges.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#206
No description provided.