Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions apps/server/src/terminal/Layers/Manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,38 @@ describe("TerminalManager", () => {
manager.dispose();
});

it("silently drops write to exited terminal instead of throwing", async () => {
const { manager, ptyAdapter } = makeManager();
await manager.open(openInput());
const process = ptyAdapter.processes[0];
expect(process).toBeDefined();
if (!process) return;

process.emitExit({ exitCode: 0, signal: 0 });

await expect(
manager.write({ threadId: "thread-1", data: "hello\n" }),
).resolves.toBeUndefined();

manager.dispose();
});

it("silently drops resize to exited terminal instead of throwing", async () => {
const { manager, ptyAdapter } = makeManager();
await manager.open(openInput());
const process = ptyAdapter.processes[0];
expect(process).toBeDefined();
if (!process) return;

process.emitExit({ exitCode: 0, signal: 0 });

await expect(
manager.resize({ threadId: "thread-1", cols: 200, rows: 50 }),
).resolves.toBeUndefined();

manager.dispose();
});

it("starts zsh with prompt spacer disabled to avoid `%` end markers", async () => {
if (process.platform === "win32") return;
const { manager, ptyAdapter } = makeManager(5, {
Expand Down
20 changes: 12 additions & 8 deletions apps/server/src/terminal/Layers/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,25 +438,29 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>
const input = decodeTerminalWriteInput(raw);
const session = this.requireSession(input.threadId, input.terminalId);
if (!session.process || session.status !== "running") {
throw new Error(
`Terminal is not running for thread: ${input.threadId}, terminal: ${input.terminalId}`,
);
return;
}
try {
session.process.write(input.data);
} catch {
// Process may have exited between the status check and the write call.
}
session.process.write(input.data);
}

async resize(raw: TerminalResizeInput): Promise<void> {
const input = decodeTerminalResizeInput(raw);
const session = this.requireSession(input.threadId, input.terminalId);
if (!session.process || session.status !== "running") {
throw new Error(
`Terminal is not running for thread: ${input.threadId}, terminal: ${input.terminalId}`,
);
return;
}
session.cols = input.cols;
session.rows = input.rows;
session.updatedAt = new Date().toISOString();
session.process.resize(input.cols, input.rows);
try {
session.process.resize(input.cols, input.rows);
} catch {
// Process may have exited between the status check and the resize call.
Comment on lines 455 to +462

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low Layers/Manager.ts:455

Consider moving session.cols and session.rows assignments inside the try block, after session.process.resize() succeeds. Currently, if resize() throws, the session state will reflect dimensions the PTY never adopted.

-    session.cols = input.cols;
-    session.rows = input.rows;
-    session.updatedAt = new Date().toISOString();
     try {
       session.process.resize(input.cols, input.rows);
+      session.cols = input.cols;
+      session.rows = input.rows;
+      session.updatedAt = new Date().toISOString();
     } catch {
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/terminal/Layers/Manager.ts around lines 455-462:

Consider moving `session.cols` and `session.rows` assignments inside the `try` block, after `session.process.resize()` succeeds. Currently, if `resize()` throws, the session state will reflect dimensions the PTY never adopted.

Evidence trail:
apps/server/src/terminal/Layers/Manager.ts lines 450-464 at REVIEWED_COMMIT: The `resize` method shows `session.cols = input.cols` (line 456) and `session.rows = input.rows` (line 457) are assigned BEFORE the `try` block. The `session.process.resize(input.cols, input.rows)` call is inside the `try` block (line 460). The catch block comment confirms the known failure mode: "Process may have exited between the status check and the resize call."

}
}

async clear(raw: TerminalClearInput): Promise<void> {
Expand Down