Re-enables the macos-container backend on the shared per-host orchestrator +
gateway, replacing the per-bottle companion container removed in #385. This is
the last backend in PRD 0070's roadmap.
Apple Container 1.0.0 forced three departures from the docker shape, each
verified against the live CLI (findings recorded in the networking spike):
- No `--ip`. The address is DHCP-assigned and knowable only once the container
runs, so the order inverts: gateway up -> run agent -> read its address ->
register. The identity token is minted by registration and therefore cannot
be in the agent's run-time env; it rides the proxy URL applied at
`container exec` time (bare `--env` names keep it off argv).
- No container DNS. The gateway can only be handed the control plane's IP, so
the orchestrator starts first and the gateway is pointed at its address.
- No `network connect`. Networks are fixed at run time, so the shared host-only
network is created up front; per-bottle networks would restart the gateway
on every launch and defeat the consolidation.
The agent runs with `--cap-drop CAP_NET_RAW`: Apple grants NET_RAW by default,
which would let an agent forge a neighbour's source address on the shared
segment. NET_ADMIN is already absent, so this closes the source-address half of
PRD 0070's attribution invariant.
Verified end-to-end on real Apple Container 1.0.0: both images build, the
control plane comes up healthy, the gateway reaches it by IP, and a registered
agent gets 200 for a host in its routes and 403 for one outside them. Bring-up
is idempotent — a second launch does not churn the singletons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apple's container exec --interactive --tty does not put the host
terminal into raw mode before starting its I/O relay. In cooked
(canonical) mode the kernel line discipline buffers modifier-key
escape sequences — e.g. Shift+Enter in modifyOtherKeys mode generates
\x1b[13;2~ — until a carriage-return arrives, so they never reach
Claude Code inside the container.
Add pty_forward.py, a stdlib-only wrapper (modelled on the existing
smolmachines pty_resize.py) that sets the host terminal to raw mode
via tty.setraw(), spawns the container exec command, and restores the
original terminal attributes on exit. Falls back to a bare
subprocess.run when stdin is not a TTY (piped invocations, CI) or
when termios operations fail.
Also retain the --env TERM=<host> forwarding from the previous commit:
without TERM inside the container session, Claude Code cannot determine
which modifier-key protocol to enable even with raw mode correctly set.
Non-TTY exec paths (bottle.exec, cp_in) are unaffected.
Without TERM, Claude Code inside the container cannot determine which
modifier-key protocol to enable (modifyOtherKeys / kitty). The inner
PTY session has no terminal-type context, so Shift+Enter and Enter
produce identical byte sequences (\r), making them indistinguishable.
Pass the host TERM via --env TERM=<value> on every container exec
--interactive --tty call, falling back to xterm-256color when TERM
is not set on the host. Non-TTY exec paths are unaffected.
Closes#245