From ccb530310633c84286e1ab106cf5cacc3d7a215c Mon Sep 17 00:00:00 2001 From: ezl-keygraph Date: Mon, 20 Apr 2026 14:45:42 +0530 Subject: [PATCH] fix(cli): surface docker errors and add --debug flag for worker logs (#299) * fix(cli): surface docker run errors and add --debug flag for worker inspection * docs: add --debug flag to CLAUDE.md options list --- CLAUDE.md | 2 +- apps/cli/src/commands/start.ts | 28 ++++++++++++++++++++++++---- apps/cli/src/docker.ts | 14 +++++++++++--- apps/cli/src/index.ts | 7 +++++++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d398d6a..54f33e4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,7 +82,7 @@ pnpm biome:fix # Auto-fix lint, format, and import sorting **Monorepo tooling:** pnpm workspaces, Turborepo for task orchestration, Biome for linting/formatting. TypeScript compiler options shared via `tsconfig.base.json` at the root. All packages extend it, overriding only `rootDir` and `outDir`. Shared devDependencies (`typescript`, `@types/node`, `turbo`, `@biomejs/biome`) are hoisted to the root workspace. -**Options:** `-c ` (YAML config), `-o ` (output directory), `-w ` (named workspace; auto-resumes if exists), `--pipeline-testing` (minimal prompts, 10s retries) +**Options:** `-c ` (YAML config), `-o ` (output directory), `-w ` (named workspace; auto-resumes if exists), `--pipeline-testing` (minimal prompts, 10s retries), `--debug` (preserve worker container after exit for log inspection) ## Architecture diff --git a/apps/cli/src/commands/start.ts b/apps/cli/src/commands/start.ts index d8f989e..242d1e4 100644 --- a/apps/cli/src/commands/start.ts +++ b/apps/cli/src/commands/start.ts @@ -22,6 +22,7 @@ export interface StartArgs { workspace?: string; output?: string; pipelineTesting: boolean; + debug: boolean; version: string; } @@ -110,14 +111,22 @@ export async function start(args: StartArgs): Promise { ...(outputDir && { outputDir }), workspace, ...(args.pipelineTesting && { pipelineTesting: true }), + ...(args.debug && { debug: true }), }); - // 14. Wait for workflow to register, then display info - proc.on('error', (err) => { - console.error(`Failed to start worker: ${err.message}`); - process.exit(1); + // 14. Bail if `docker run -d` itself fails (mount error, image missing, etc.) + const dockerExitCode = await new Promise((resolve) => { + proc.once('exit', (code) => resolve(code ?? 1)); + proc.once('error', (err) => { + console.error(`Failed to start worker: ${err.message}`); + resolve(1); + }); }); + if (dockerExitCode !== 0) { + process.exit(1); + } + // Detect whether this is a fresh workspace or a resume by checking session.json existence const sessionJson = path.join(workspacesDir, workspace, 'session.json'); const isResume = fs.existsSync(sessionJson); @@ -182,6 +191,9 @@ export async function start(args: StartArgs): Promise { } catch { // Container may have already exited } + if (args.debug) { + printDebugHint(containerName); + } }; process.on('SIGINT', () => { @@ -195,6 +207,14 @@ export async function start(args: StartArgs): Promise { process.on('exit', cleanup); } +function printDebugHint(containerName: string): void { + console.log(''); + console.log(` Worker container preserved: ${containerName}`); + console.log(` Inspect logs: docker logs ${containerName}`); + console.log(` Remove: docker rm ${containerName}`); + console.log(''); +} + function printInfo( args: StartArgs, workspace: string, diff --git a/apps/cli/src/docker.ts b/apps/cli/src/docker.ts index 84c3cc4..00ecbfe 100644 --- a/apps/cli/src/docker.ts +++ b/apps/cli/src/docker.ts @@ -159,13 +159,19 @@ export interface WorkerOptions { outputDir?: string; workspace: string; pipelineTesting?: boolean; + debug?: boolean; } /** * Spawn the worker container in detached mode and return the process. + * When `opts.debug` is true, omits `--rm` so the container persists for log inspection. */ export function spawnWorker(opts: WorkerOptions): ChildProcess { - const args = ['run', '-d', '--rm', '--name', opts.containerName, '--network', 'shannon-net']; + const args = ['run', '-d']; + if (!opts.debug) { + args.push('--rm'); + } + args.push('--name', opts.containerName, '--network', 'shannon-net'); // Add host flag for Linux args.push(...addHostFlag()); @@ -227,9 +233,11 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess { args.push('--pipeline-testing'); } - // Prevent MSYS/Git Bash from converting Unix paths (e.g. /repos/my-repo) to Windows paths + // Inherit stderr so `docker run` daemon errors surface to the user; + // ignore stdin/stdout (the container ID is noise). return spawn('docker', args, { - stdio: 'pipe', + stdio: ['ignore', 'ignore', 'inherit'], + // Prevent MSYS/Git Bash from converting Unix paths on Windows ...(os.platform() === 'win32' && { env: { ...process.env, MSYS_NO_PATHCONV: '1' } }), }); } diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 5c87698..53d8182 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -69,6 +69,7 @@ Options for 'start': -o, --output Copy deliverables to this directory after run -w, --workspace Named workspace (auto-resumes if exists) --pipeline-testing Use minimal prompts for fast testing + --debug Preserve worker container after exit for log inspection Examples: ${prefix} start -u https://example.com -r ${mode === 'local' ? 'my-repo' : './my-repo'} @@ -93,6 +94,7 @@ interface ParsedStartArgs { workspace?: string; output?: string; pipelineTesting: boolean; + debug: boolean; } function parseStartArgs(argv: string[]): ParsedStartArgs { @@ -102,6 +104,7 @@ function parseStartArgs(argv: string[]): ParsedStartArgs { let workspace: string | undefined; let output: string | undefined; let pipelineTesting = false; + let debug = false; for (let i = 0; i < argv.length; i++) { const arg = argv[i]; @@ -146,6 +149,9 @@ function parseStartArgs(argv: string[]): ParsedStartArgs { case '--pipeline-testing': pipelineTesting = true; break; + case '--debug': + debug = true; + break; default: console.error(`Unknown option: ${arg}`); console.error(`Run "${getMode() === 'local' ? './shannon' : 'npx @keygraph/shannon'} help" for usage`); @@ -163,6 +169,7 @@ function parseStartArgs(argv: string[]): ParsedStartArgs { url, repo, pipelineTesting, + debug, ...(config && { config }), ...(workspace && { workspace }), ...(output && { output }),