mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
When the web server is launched from inside a tmux pane it inherits TMUX/
TMUX_PANE. tmux's nesting guard then makes every new attach-bridge PTY
(`tmux attach-session`, used by codex/opencode/gemini and mux-wrapped claude)
exit code 1; the respawn controller recreates the dead bridge → infinite loop.
The existing guard in buildMuxAttachEnv() used `TMUX: undefined` on a
{...process.env} spread, which leaves the KEY present with value undefined —
node-pty serializes it as the literal string "TMUX=undefined", still tripping
the guard. (The working create path in tmux-manager.ts uses `delete`.)
Fix:
- Primary: delete process.env.TMUX / TMUX_PANE at web bootstrap (src/index.ts)
so every downstream {...process.env} spread is clean regardless of launch
context. `delete`, not `= undefined`.
- buildMuxAttachEnv(): build a copy and `delete` TMUX/TMUX_PANE/CLAUDECODE
(and COLORTERM when not truecolor) instead of `: undefined` — same node-pty
quirk affected all of them.
- Test: assert the keys are genuinely ABSENT (`'TMUX' in env === false`), not
merely undefined — the prior test only checked `toBeUndefined()`, which is
why the bug slipped through. Red→green confirmed.
Verified on isolated beta launched from inside tmux (inherited the poisonous
TMUX=codeman,980,7): created a codex session + triggered interactive attach —
the bridge `tmux -L codeman-beta attach-session` spawned with NO TMUX in its
env, attached successfully, zero "exited with code: 1", server healthy.
Circuit-breaker for repeated non-zero bridge exits (AC bullet 4, optional)
split to a follow-up. Deploy-pending (substrate): never auto-deployed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
2.2 KiB
JavaScript
71 lines
2.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* @fileoverview Codeman CLI entry point
|
|
*
|
|
* This is the main executable entry point for the Codeman CLI.
|
|
* It sets up global error handlers and invokes the CLI parser.
|
|
*
|
|
* @module index
|
|
*/
|
|
|
|
import { program } from './cli.js';
|
|
|
|
// Detect if we're running the web server (long-lived process)
|
|
// In web mode, we should NOT exit on transient errors — log and continue
|
|
const isWebMode = process.argv.includes('web');
|
|
|
|
// COD-115: Codeman IS a tmux controller; it must never present as a tmux *client*.
|
|
// If the web server is launched from inside a tmux pane it inherits TMUX/TMUX_PANE,
|
|
// and tmux's nesting guard then kills every new attach-bridge PTY (exit 1 → respawn
|
|
// loop, crash-looping any new tmux-backed session). Scrub at the root so every
|
|
// downstream `{...process.env}` spread (attach, send-keys, create) is clean regardless
|
|
// of launch context. `delete` (not `= undefined`, which node-pty serializes as the
|
|
// literal string "undefined" and fails to clear).
|
|
if (isWebMode) {
|
|
delete process.env.TMUX;
|
|
delete process.env.TMUX_PANE;
|
|
}
|
|
|
|
import { MAX_CONSECUTIVE_ERRORS, ERROR_RESET_MS } from './config/server-timing.js';
|
|
|
|
// Track consecutive unhandled errors in web mode — restart after too many
|
|
let consecutiveErrors = 0;
|
|
let errorResetTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
function trackError(): void {
|
|
consecutiveErrors++;
|
|
if (errorResetTimer) clearTimeout(errorResetTimer);
|
|
errorResetTimer = setTimeout(() => {
|
|
consecutiveErrors = 0;
|
|
}, ERROR_RESET_MS);
|
|
|
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
console.error(`[FATAL] ${MAX_CONSECUTIVE_ERRORS} consecutive unhandled errors — exiting for systemd restart`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Handle uncaught errors
|
|
process.on('uncaughtException', (err) => {
|
|
console.error('Uncaught exception:', err.message);
|
|
if (isWebMode) {
|
|
console.error('[RECOVERED] Server continuing after uncaught exception:', err.stack);
|
|
trackError();
|
|
} else {
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
process.on('unhandledRejection', (reason) => {
|
|
console.error('Unhandled rejection:', reason);
|
|
if (isWebMode) {
|
|
console.error('[RECOVERED] Server continuing after unhandled rejection');
|
|
trackError();
|
|
} else {
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
// Run CLI
|
|
program.parse();
|