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
@@ -6,6 +6,7 @@ import {
listPanes, listPanes,
findWindowByName, findWindowByName,
capturePaneContent, capturePaneContent,
extractExitCodeMarkerFromOutput,
sendText as tmuxSendText, sendText as tmuxSendText,
sendKeys as tmuxSendKeys, sendKeys as tmuxSendKeys,
renameWindow, renameWindow,
@@ -382,7 +383,9 @@ export class TerminalManager {
// Use stored values for dead panes, current values for live ones // Use stored values for dead panes, current values for live ones
const workingDirectory = await getStoredWorkingDirectory(window.id, paneId); const workingDirectory = await getStoredWorkingDirectory(window.id, paneId);
const currentCommand = await getStoredCommand(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; let exitCode: number | null = null;
if (isDead) { if (isDead) {
@@ -394,7 +397,7 @@ export class TerminalManager {
"#{pane_dead_status}", "#{pane_dead_status}",
]); ]);
const parsed = parseInt(exitCodeStr, 10); const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null; exitCode = Number.isFinite(parsed) ? parsed : lastLinesExtracted.exitCode;
} }
commands.push({ commands.push({
@@ -427,9 +430,11 @@ export class TerminalManager {
throw new Error(`Session '${this.sessionName}' not found.`); 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([ const deadStatus = await executeTmux([
"display-message", "display-message",
@@ -450,7 +455,7 @@ export class TerminalManager {
"#{pane_dead_status}", "#{pane_dead_status}",
]); ]);
const parsed = parseInt(exitCodeStr, 10); const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null; exitCode = Number.isFinite(parsed) ? parsed : extracted.exitCode;
} }
return { return {
@@ -49,6 +49,8 @@ let shellConfig: { type: ShellType } = { type: "bash" };
const ANSI_ESCAPE_REGEX = const ANSI_ESCAPE_REGEX =
/\u001B[\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; /\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 { function stripAnsiSequences(value: string): string {
if (!value) { if (!value) {
return ""; return "";
@@ -56,6 +58,33 @@ function stripAnsiSequences(value: string): string {
return value.replace(ANSI_ESCAPE_REGEX, ""); 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 { export function setShellConfig(config: { type: string }): void {
// Validate shell type // Validate shell type
const validShells: ShellType[] = ["bash", "zsh", "fish"]; const validShells: ShellType[] = ["bash", "zsh", "fish"];
@@ -559,7 +588,7 @@ export async function executeCommand({
// Execute command via respawn-pane with bash -c wrapper // Execute command via respawn-pane with bash -c wrapper
// This ensures all bash features work (pipes, operators, etc.) // 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]); await executeTmux(["respawn-pane", "-t", pane.id, "-k", wrappedCommand]);
// Wait for command to finish or reach stability // Wait for command to finish or reach stability
@@ -584,6 +613,8 @@ export async function executeCommand({
if (isDead) { if (isDead) {
// Command finished - capture output and exit code // Command finished - capture output and exit code
output = await capturePaneContent(pane.id, 1000, false); output = await capturePaneContent(pane.id, 1000, false);
const extracted = extractExitCodeMarkerFromOutput(output);
output = extracted.output;
const exitCodeStr = await executeTmux([ const exitCodeStr = await executeTmux([
"display-message", "display-message",
"-p", "-p",
@@ -592,7 +623,7 @@ export async function executeCommand({
"#{pane_dead_status}", "#{pane_dead_status}",
]); ]);
const parsed = parseInt(exitCodeStr, 10); const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null; exitCode = Number.isFinite(parsed) ? parsed : extracted.exitCode;
break; break;
} }