Persist exit codes without tmux pane_dead_status

This commit is contained in:
Mohamed Boudra
2026-02-05 00:18:48 +07:00
parent 2620fda500
commit 6e882aaceb
2 changed files with 43 additions and 7 deletions

View File

@@ -6,6 +6,7 @@ import {
listPanes,
findWindowByName,
capturePaneContent,
extractExitCodeMarkerFromOutput,
sendText as tmuxSendText,
sendKeys as tmuxSendKeys,
renameWindow,
@@ -382,7 +383,9 @@ export class TerminalManager {
// Use stored values for dead panes, current values for live ones
const workingDirectory = await getStoredWorkingDirectory(window.id, paneId);
const currentCommand = await getStoredCommand(window.id, paneId);
const lastLines = await capturePaneContent(paneId, 5, false);
const rawLastLines = await capturePaneContent(paneId, 5, false);
const lastLinesExtracted = extractExitCodeMarkerFromOutput(rawLastLines);
const lastLines = lastLinesExtracted.output;
let exitCode: number | null = null;
if (isDead) {
@@ -394,7 +397,7 @@ export class TerminalManager {
"#{pane_dead_status}",
]);
const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null;
exitCode = Number.isFinite(parsed) ? parsed : lastLinesExtracted.exitCode;
}
commands.push({
@@ -427,9 +430,11 @@ export class TerminalManager {
throw new Error(`Session '${this.sessionName}' not found.`);
}
const paneId = `${commandId}.0`;
const paneId = `${commandId}.0`;
const output = await capturePaneContent(paneId, lines, false);
const rawOutput = await capturePaneContent(paneId, lines, false);
const extracted = extractExitCodeMarkerFromOutput(rawOutput);
const output = extracted.output;
const deadStatus = await executeTmux([
"display-message",
@@ -450,7 +455,7 @@ export class TerminalManager {
"#{pane_dead_status}",
]);
const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null;
exitCode = Number.isFinite(parsed) ? parsed : extracted.exitCode;
}
return {

View File

@@ -49,6 +49,8 @@ let shellConfig: { type: ShellType } = { type: "bash" };
const ANSI_ESCAPE_REGEX =
/\u001B[\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
const EXIT_CODE_MARKER = "__PASEO_EXIT_CODE__:";
function stripAnsiSequences(value: string): string {
if (!value) {
return "";
@@ -56,6 +58,33 @@ function stripAnsiSequences(value: string): string {
return value.replace(ANSI_ESCAPE_REGEX, "");
}
export function extractExitCodeMarkerFromOutput(output: string): {
exitCode: number | null;
output: string;
} {
if (!output) {
return { exitCode: null, output };
}
let exitCode: number | null = null;
const kept: string[] = [];
for (const line of output.split("\n")) {
const trimmed = line.trim();
if (trimmed.startsWith(EXIT_CODE_MARKER)) {
const codeStr = trimmed.slice(EXIT_CODE_MARKER.length).trim();
const parsed = parseInt(codeStr, 10);
if (Number.isFinite(parsed)) {
exitCode = parsed;
}
continue;
}
kept.push(line);
}
return { exitCode, output: kept.join("\n").trimEnd() };
}
export function setShellConfig(config: { type: string }): void {
// Validate shell type
const validShells: ShellType[] = ["bash", "zsh", "fish"];
@@ -559,7 +588,7 @@ export async function executeCommand({
// Execute command via respawn-pane with bash -c wrapper
// This ensures all bash features work (pipes, operators, etc.)
const wrappedCommand = `bash -c 'cd "${expandedPath}" && ${command.replace(/'/g, "'\\''")} 2>&1; exit $?'`;
const wrappedCommand = `bash -c 'cd "${expandedPath}" && ${command.replace(/'/g, "'\\''")} 2>&1; code=$?; echo ${EXIT_CODE_MARKER}$code; exit $code'`;
await executeTmux(["respawn-pane", "-t", pane.id, "-k", wrappedCommand]);
// Wait for command to finish or reach stability
@@ -584,6 +613,8 @@ export async function executeCommand({
if (isDead) {
// Command finished - capture output and exit code
output = await capturePaneContent(pane.id, 1000, false);
const extracted = extractExitCodeMarkerFromOutput(output);
output = extracted.output;
const exitCodeStr = await executeTmux([
"display-message",
"-p",
@@ -592,7 +623,7 @@ export async function executeCommand({
"#{pane_dead_status}",
]);
const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null;
exitCode = Number.isFinite(parsed) ? parsed : extracted.exitCode;
break;
}