mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 07:29:42 +02:00
fix(docker): verify the container workdir and end the probe with exit 0
Two defects that only a real container exposes. The probe chained `command -v X && echo X` with semicolons, and a script's exit status is its last command's. A container without the last probed CLI made the whole `sh -lc` exit 1, so a perfectly healthy container with tmux and claude was reported as "could not exec into the container". A missing CLI is data here, not failure, so the script now ends with `exit 0`. containerWorkdir defaulted to hostWorkspacePath. That default holds for an owned container only because the create-time bind mount puts the host directory at that exact path; attaching mounts nothing, so the two are independent facts. A host path absent inside the container makes `docker exec --workdir` fail with an OCI chdir error that surfaces in the pane as a bare "execvp failed". The preflight now proves the directory exists inside the container and refuses at link time.
This commit is contained in:
+37
-3
@@ -1044,6 +1044,8 @@ export interface AdoptedContainerProbe {
|
||||
tmuxPath?: string;
|
||||
/** Modes whose CLI resolved inside the container (`command -v <mode>`). */
|
||||
availableModes?: SessionMode[];
|
||||
/** Whether the requested working directory exists INSIDE the container. */
|
||||
workdirExists?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@@ -1059,10 +1061,18 @@ export interface AdoptedContainerProbe {
|
||||
*/
|
||||
export async function probeAdoptableContainer(
|
||||
docker: Pick<SessionDocker, 'engine' | 'context' | 'daemonHost' | 'containerName'>,
|
||||
modes: SessionMode[] = []
|
||||
modes: SessionMode[] = [],
|
||||
containerWorkdir?: string
|
||||
): Promise<AdoptedContainerProbe> {
|
||||
if (IS_TEST_MODE) {
|
||||
return { ok: true, exists: true, running: true, tmuxPath: '/usr/bin/tmux', availableModes: modes };
|
||||
return {
|
||||
ok: true,
|
||||
exists: true,
|
||||
running: true,
|
||||
tmuxPath: '/usr/bin/tmux',
|
||||
availableModes: modes,
|
||||
workdirExists: true,
|
||||
};
|
||||
}
|
||||
const argv = dockerEngineArgv(docker);
|
||||
let running = false;
|
||||
@@ -1096,7 +1106,19 @@ export async function probeAdoptableContainer(
|
||||
// One exec resolves tmux plus every requested CLI, so adoption costs a single
|
||||
// round trip. Binaries are fixed mode names, never user input.
|
||||
const probes = ['tmux', ...modes.filter((m) => m !== 'shell')];
|
||||
const script = probes.map((bin) => `command -v ${bin} >/dev/null 2>&1 && echo ${bin}`).join('; ');
|
||||
// `; exit 0` is load-bearing: the script's status is its LAST command's, so a
|
||||
// missing final CLI made the whole `sh -lc` exit 1 and the probe reported
|
||||
// "could not exec into the container" for a container that was perfectly fine.
|
||||
// Absence of a CLI is data here, not failure — only a real exec error is.
|
||||
const steps = probes.map((bin) => `command -v ${bin} >/dev/null 2>&1 && echo ${bin}`);
|
||||
// The workdir is checked INSIDE the container, and that is a fact independent
|
||||
// of hostWorkspacePath: an owned container gets the host dir bind-mounted at the
|
||||
// same absolute path at create time, but adoption mounts nothing, so the two
|
||||
// paths only coincide if the user mounted it there themselves. `docker exec
|
||||
// --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__`);
|
||||
const script = `${steps.join('; ')}; exit 0`;
|
||||
try {
|
||||
const { stdout } = await execFileAsync(
|
||||
argv[0],
|
||||
@@ -1118,6 +1140,17 @@ export async function probeAdoptableContainer(
|
||||
error: `container "${docker.containerName}" has no tmux (required for durable sessions; install it inside the container)`,
|
||||
};
|
||||
}
|
||||
const workdirExists = containerWorkdir ? found.has('__workdir__') : undefined;
|
||||
if (containerWorkdir && !workdirExists) {
|
||||
return {
|
||||
ok: false,
|
||||
exists: true,
|
||||
running: true,
|
||||
image,
|
||||
workdirExists: false,
|
||||
error: `"${containerWorkdir}" does not exist inside container "${docker.containerName}". Adoption mounts nothing, so the container workdir must already exist there — set it to a path inside the container (it need not match the host workspace path).`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
exists: true,
|
||||
@@ -1125,6 +1158,7 @@ export async function probeAdoptableContainer(
|
||||
image,
|
||||
tmuxPath: 'tmux',
|
||||
availableModes: modes.filter((m) => m === 'shell' || found.has(m)),
|
||||
workdirExists,
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
|
||||
Reference in New Issue
Block a user