fix(docker): set CLAUDE_CODE_TMPDIR + document hook reachability limit

Found in live testing: claude refuses its default /tmp/claude-<uid> temp dir when
that path pre-exists root-owned (happens when the workspace bind-mount traverses
it, e.g. a workspace under /tmp/claude-<uid>). Set CLAUDE_CODE_TMPDIR to a
nonexistent HOME subpath the running uid creates+owns, so docker claude sessions
are robust to any workspace location.

Also document the hook-reachability constraint: in-container hooks POST to
host.docker.internal (the bridge gateway), so they only fire when Codeman is
reachable from the container (bind 0.0.0.0 + password); on a loopback-only bind
they don't fire and idle detection falls back to output-based (which works).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-07-19 18:19:50 +02:00
parent 8b2c857c3f
commit a36c1f62db
2 changed files with 15 additions and 0 deletions
+9
View File
@@ -60,6 +60,15 @@ The container is paused across the capture so the image and workspace are consis
`GET /api/docker-exports` lists bundles; `GET /api/docker-exports/:filename` downloads one; `DELETE` removes one.
## Hooks require the server to be reachable from the container
In-container hooks (permission events, hook-based idle/stop/task notifications) POST to `CODEMAN_API_URL`, which is derived as `https://host.docker.internal:<port>` (`host.docker.internal` → the docker bridge gateway, e.g. `172.17.0.1`, via `--add-host …:host-gateway`). For that callback to succeed, the Codeman server must be **listening on an interface the container can reach**.
- If Codeman binds **loopback-only** (`127.0.0.1`, the default and the production systemd config), a container reaching `172.17.0.1:<port>` cannot connect, so **in-container hooks do not fire**. The session still works fully: idle/stop detection falls back to **output-based** detection through the `docker exec` PTY (which always works), and claude runs with `--dangerously-skip-permissions` so there are no permission prompts to forward anyway.
- To enable in-container hooks, run Codeman where the container can reach it: bind `0.0.0.0` **with `CODEMAN_PASSWORD` set** (`CODEMAN_HOST=0.0.0.0`), or otherwise make `172.17.0.1:<port>` reachable. The host guard already allowlists `host.docker.internal` / `host.containers.internal`, and the hook secret is mounted, so hooks work as soon as the callback is reachable.
This is an environmental constraint, not a code limitation: the host-gateway mapping, `CODEMAN_API_URL` derivation, host-guard allowlist, and hook-secret mount are all wired correctly.
## Notes & limits
- Requires Docker (or Podman) with a reachable daemon; tmux must be present in the base image (a hard prerequisite, probed at link time).
+6
View File
@@ -997,6 +997,12 @@ export function resolveDockerLaunchOptions(
HOME: CONTAINER_HOME,
TERM: 'xterm-256color',
COLORTERM: 'truecolor',
// Give claude a temp dir it will own inside HOME. Its default `/tmp/claude-<uid>`
// is refused when that path pre-exists root-owned — which happens when the
// workspace bind-mount path traverses it (e.g. a workspace under /tmp/claude-<uid>).
// A nonexistent HOME subpath is created+owned by the running uid, so this is robust
// to any workspace location. Non-secret path, safe to be committed on export.
CLAUDE_CODE_TMPDIR: `${CONTAINER_HOME}/.cache/codeman-claude-tmp`,
};
if (docker.hooksEnabled) {
// Derive a container-reachable API url (scheme + port preserved; host swapped