backport: surface docker errors and add --debug flag for worker logs

Cherry-pick of KeygraphHQ/shannon#299 (ccb5303).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 13:32:49 -04:00
parent c7be324083
commit 06a6b15e4c
3 changed files with 42 additions and 7 deletions
+24 -4
View File
@@ -22,6 +22,7 @@ export interface StartArgs {
workspace?: string; workspace?: string;
output?: string; output?: string;
pipelineTesting: boolean; pipelineTesting: boolean;
debug: boolean;
version: string; version: string;
} }
@@ -110,14 +111,22 @@ export async function start(args: StartArgs): Promise<void> {
...(outputDir && { outputDir }), ...(outputDir && { outputDir }),
workspace, workspace,
...(args.pipelineTesting && { pipelineTesting: true }), ...(args.pipelineTesting && { pipelineTesting: true }),
...(args.debug && { debug: true }),
}); });
// 14. Wait for workflow to register, then display info // 14. Bail if `docker run -d` itself fails (mount error, image missing, etc.)
proc.on('error', (err) => { const dockerExitCode = await new Promise<number>((resolve) => {
console.error(`Failed to start worker: ${err.message}`); proc.once('exit', (code) => resolve(code ?? 1));
process.exit(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 // Detect whether this is a fresh workspace or a resume by checking session.json existence
const sessionJson = path.join(workspacesDir, workspace, 'session.json'); const sessionJson = path.join(workspacesDir, workspace, 'session.json');
const isResume = fs.existsSync(sessionJson); const isResume = fs.existsSync(sessionJson);
@@ -182,6 +191,9 @@ export async function start(args: StartArgs): Promise<void> {
} catch { } catch {
// Container may have already exited // Container may have already exited
} }
if (args.debug) {
printDebugHint(containerName);
}
}; };
process.on('SIGINT', () => { process.on('SIGINT', () => {
@@ -195,6 +207,14 @@ export async function start(args: StartArgs): Promise<void> {
process.on('exit', cleanup); 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( function printInfo(
args: StartArgs, args: StartArgs,
workspace: string, workspace: string,
+11 -3
View File
@@ -159,13 +159,19 @@ export interface WorkerOptions {
outputDir?: string; outputDir?: string;
workspace: string; workspace: string;
pipelineTesting?: boolean; pipelineTesting?: boolean;
debug?: boolean;
} }
/** /**
* Spawn the worker container in detached mode and return the process. * 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 { 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 // Add host flag for Linux
args.push(...addHostFlag()); args.push(...addHostFlag());
@@ -227,9 +233,11 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
args.push('--pipeline-testing'); 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, { 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' } }), ...(os.platform() === 'win32' && { env: { ...process.env, MSYS_NO_PATHCONV: '1' } }),
}); });
} }
+7
View File
@@ -70,6 +70,7 @@ Options for 'start':
-o, --output <path> Copy deliverables to this directory after run -o, --output <path> Copy deliverables to this directory after run
-w, --workspace <name> Named workspace (auto-resumes if exists) -w, --workspace <name> Named workspace (auto-resumes if exists)
--pipeline-testing Use minimal prompts for fast testing --pipeline-testing Use minimal prompts for fast testing
--debug Preserve worker container after exit for log inspection
Examples: Examples:
${prefix} start -u https://example.com -r ${mode === 'local' ? 'my-repo' : './my-repo'} ${prefix} start -u https://example.com -r ${mode === 'local' ? 'my-repo' : './my-repo'}
@@ -94,6 +95,7 @@ interface ParsedStartArgs {
workspace?: string; workspace?: string;
output?: string; output?: string;
pipelineTesting: boolean; pipelineTesting: boolean;
debug: boolean;
} }
function parseStartArgs(argv: string[]): ParsedStartArgs { function parseStartArgs(argv: string[]): ParsedStartArgs {
@@ -103,6 +105,7 @@ function parseStartArgs(argv: string[]): ParsedStartArgs {
let workspace: string | undefined; let workspace: string | undefined;
let output: string | undefined; let output: string | undefined;
let pipelineTesting = false; let pipelineTesting = false;
let debug = false;
for (let i = 0; i < argv.length; i++) { for (let i = 0; i < argv.length; i++) {
const arg = argv[i]; const arg = argv[i];
@@ -147,6 +150,9 @@ function parseStartArgs(argv: string[]): ParsedStartArgs {
case '--pipeline-testing': case '--pipeline-testing':
pipelineTesting = true; pipelineTesting = true;
break; break;
case '--debug':
debug = true;
break;
default: default:
console.error(`Unknown option: ${arg}`); console.error(`Unknown option: ${arg}`);
console.error(`Run "${getMode() === 'local' ? './shannon' : 'npx @keygraph/shannon'} help" for usage`); console.error(`Run "${getMode() === 'local' ? './shannon' : 'npx @keygraph/shannon'} help" for usage`);
@@ -164,6 +170,7 @@ function parseStartArgs(argv: string[]): ParsedStartArgs {
url, url,
repo, repo,
pipelineTesting, pipelineTesting,
debug,
...(config && { config }), ...(config && { config }),
...(workspace && { workspace }), ...(workspace && { workspace }),
...(output && { output }), ...(output && { output }),