mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 06:59:42 +02:00
fix(docker): stop requiring the CLI on the host for a container session
Attaching a container, picking claude and hitting Run gave one line — `execvp(3) failed.: No such file or directory` — and the run-mode menu offered every mode. Three separate defects, found on a real deployment. TmuxManager.createSession resolved the CLI directory without distinguishing a docker session, so a host with no claude threw, the catch fell back to a direct PTY, and that PTY exec'd the CLI on the HOST. The failure surfaced as a bare execvp error naming nothing. A docker session runs its CLI inside the container; the host does not need it. All eight modes now sit behind a cliRunsInContainer guard, and whether the container has the CLI is settled by the adoption preflight or the image gate before launch. The running check used a bare double quote and command substitution. The whole chain is embedded in an outer `bash -c "…"`, so the unescaped quote closed that string early and the remainder was re-tokenized. It is now a `grep -qx` pipeline using only the single-quote form every other line in the builder already uses. Claude Code refuses --dangerously-skip-permissions as root. Our base image runs a non-root user, so an owned container never hit this; an adopted container's user belongs to its owner and is frequently root, and keeping the flag killed the pane with a message visible only inside the container. The preflight now reports runsAsRoot and the launch chain drops the flag for it. The menu also showed every mode because the container CLI probe only started when the menu opened. It is warmed when the case is selected instead.
This commit is contained in:
+15
-3
@@ -160,11 +160,16 @@ export function dockerContainerName(caseName: string): string {
|
||||
}
|
||||
|
||||
/** Default pane command per CLI mode (mirror of defaultRemoteCommandForMode). */
|
||||
export function defaultDockerCommandForMode(mode: SessionMode): string {
|
||||
export function defaultDockerCommandForMode(mode: SessionMode, runsAsRoot = false): string {
|
||||
const commands: Record<DockerCommandMode, string> = {
|
||||
shell: 'exec bash -l',
|
||||
// Mirror the LOCAL claude default so the in-container agent runs non-interactively.
|
||||
claude: 'exec claude --dangerously-skip-permissions',
|
||||
// Mirror the LOCAL claude default so the in-container agent runs
|
||||
// non-interactively — EXCEPT as root, where Claude Code refuses the flag
|
||||
// outright ("cannot be used with root/sudo privileges"). Our base image runs
|
||||
// a non-root user so an owned container never hits this; an adopted
|
||||
// container's user belongs to its owner and is frequently root, and keeping
|
||||
// the flag there kills the pane with a message only visible inside it.
|
||||
claude: runsAsRoot ? 'exec claude' : 'exec claude --dangerously-skip-permissions',
|
||||
opencode: 'exec opencode',
|
||||
codex: 'exec codex',
|
||||
gemini: 'exec gemini',
|
||||
@@ -1056,6 +1061,8 @@ export interface AdoptedContainerProbe {
|
||||
availableModes?: SessionMode[];
|
||||
/** Whether the requested working directory exists INSIDE the container. */
|
||||
workdirExists?: boolean;
|
||||
/** Whether the container's exec user is root (uid 0). */
|
||||
runsAsRoot?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@@ -1179,6 +1186,10 @@ export async function probeAdoptableContainer(
|
||||
// --workdir <missing>` fails with an OCI chdir error the pane surfaces as a bare
|
||||
// "execvp failed", so it is resolved here into an actionable message.
|
||||
if (containerWorkdir) steps.push(`[ -d ${shellescape(containerWorkdir)} ] && echo __workdir__`);
|
||||
// Claude Code REFUSES --dangerously-skip-permissions as root. Our own base
|
||||
// image runs a non-root user so an owned container never hits it; an adopted
|
||||
// container's user belongs to its owner and is frequently root.
|
||||
steps.push(`[ "$(id -u)" = 0 ] && echo __root__`);
|
||||
const script = `${steps.join('; ')}; exit 0`;
|
||||
try {
|
||||
const { stdout } = await execFileAsync(
|
||||
@@ -1220,6 +1231,7 @@ export async function probeAdoptableContainer(
|
||||
tmuxPath: 'tmux',
|
||||
availableModes: modes.filter((m) => m === 'shell' || found.has(binaryFor(m))),
|
||||
workdirExists,
|
||||
runsAsRoot: found.has('__root__'),
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
|
||||
Reference in New Issue
Block a user