can1357/oh-my-pi · error · Error

Failed to inspect ${holder.image} (PID ${holder.pid})

Error message

Failed to inspect ${holder.image} (PID ${holder.pid})

What it means

After SIGTERM, terminatePortHolder() polls the PID with signal 0 (existence check). If a poll fails with an errno other than ESRCH, the error is wrapped as 'Failed to inspect'. This is distinct from a kill failure: the signal was sent, but the runtime cannot even probe whether the process still exists.

Source

Thrown at packages/stats/src/port-conflict.ts:208

	if (process.platform === "win32") return findWindowsPortHolder(port);
	return null;
}

async function terminatePortHolder(holder: PortHolder): Promise<void> {
	try {
		process.kill(holder.pid, "SIGTERM");
	} catch (error) {
		if (error instanceof Error && "code" in error && error.code === "ESRCH") return;
		throw new Error(`Failed to stop ${holder.image} (PID ${holder.pid})`, { cause: error });
	}

	for (let attempt = 0; attempt < PROCESS_EXIT_POLLS; attempt++) {
		await Bun.sleep(PROCESS_EXIT_POLL_MS);
		try {
			process.kill(holder.pid, 0);
		} catch (error) {
			if (error instanceof Error && "code" in error && error.code === "ESRCH") return;
			throw new Error(`Failed to inspect ${holder.image} (PID ${holder.pid})`, { cause: error });
		}
	}

	try {
		process.kill(holder.pid, "SIGKILL");
	} catch (error) {
		if (error instanceof Error && "code" in error && error.code === "ESRCH") return;
		throw new Error(`Failed to kill ${holder.image} (PID ${holder.pid})`, { cause: error });
	}
	await Bun.sleep(PROCESS_EXIT_POLL_MS);
}

async function reclaimStatsPort(port: number): Promise<"retry"> {
	const holder = await findPortHolder(port);
	if (!holder) {
		throw new Error(`Port ${port} is in use, but the listening process could not be identified.`);
	}
	if (holder.pid === process.pid) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Grant the probing user visibility/signal rights over the target (same user, same container/namespace)
  2. Verify with ps -p <pid> whether the process actually exited despite the probe failing
  3. Free the port without termination: choose a different port or stop the holder manually
  4. Update permissions/sandbox config so kill(pid, 0) checks are allowed

Example fix

// before
await recoverStatsPort(port); // throws 'Failed to inspect ... (PID 1234)'
// after
try {
  await recoverStatsPort(port);
} catch (err) {
  if (/Failed to inspect/.test(err.message)) {
    console.error(`Cannot probe PID; stop it manually: sudo kill <pid>, or use OMP_STATS_PORT to pick another port`);
  } else throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

try {
  process.kill(holder.pid, 0);
  console.log(`PID ${holder.pid} probe OK`);
} catch (err) {
  if (err.code === 'EPERM') console.error('kill(0) probe denied — sandbox/permission issue ahead');
}

Type guard

function isNodeErrno(err: unknown): err is NodeJS.ErrnoException {
  return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';
}

Try / catch

try {
  await recoverStatsPort(port);
} catch (err) {
  if (err.message.startsWith('Failed to inspect')) {
    console.error(`Cannot probe PID ${err.message}; verify with ps and stop manually if needed`);
  } else throw err;
}

Prevention

When it happens

Trigger: During the exit-poll loop, process.kill(holder.pid, 0) throws EPERM or another non-ESRCH errno — typically when the SIGTERM changed the process's ownership/visibility context (e.g. it re-execs under a different user) or the environment restricts signal(0) probes.

Common situations: Containers or macOS seatbelt/profiles denying kill(0) probes; the holder process being re-parented to a different UID after a graceful restart; mismatched namespaces where the PID table is partially visible.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/acd4dbd2550954c3. Report an issue: GitHub.