diff --git a/README.md b/README.md index 3e6566d..e2683cd 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ See what needs attention, resume the right conversation, and work on a paired PC -> **1.44.0** lists Claude Code's own background sessions (`claude --bg`, the agent view) in the sidebar, marks the ones waiting for you, and opens one in a tile by attaching to it ([release notes](docs/releases/1.44.0.md)). 1.43.0 runs parallel sessions in separate workspaces — each in its own git worktree and branch, so two agents never edit the same files — with its changes in the header and a clean-up that warns before unmerged work is lost ([release notes](docs/releases/1.43.0.md)). 1.42.0 shows what changed — a What's new window after each update, with Show me, and NEW dots on new buttons until you try them — and, as an experimental setting on Windows, lets Claude Code report its own state (working, needs approval, asking, done) instead of DevDeck reading the screen ([release notes](docs/releases/1.42.0.md)). 1.41.0 adds quick replies (one click or Ctrl+Shift+1…9 for the phrases you keep typing), a session timeline of your prompts with their times, replies and images, red boxes and arrows drawn on a screenshot and sent straight back to the session, and text, log and PDF attachments for sessions on another PC ([release notes](docs/releases/1.41.0.md)). 1.40.0 ties each screenshot to the terminal row it appeared on — hover a highlighted `[Image]` for a thumbnail, click for the full view, and jump from the viewer back to the row — says what led to each image, previews new screenshots, copies images, and adds Ctrl+↑/↓ between your prompts ([release notes](docs/releases/1.40.0.md)). 1.39.0 shows the screenshots and images a Claude session worked with — Claude Desktop shows them inline; DevDeck puts them in a strip beside the terminal, also for sessions on another PC — adds 📎 / drag-and-drop attachments (images travel to a remote PC), **Update all** for Claude Code on Windows, staggered session restores that no longer stall a PC, and ends the daily false "Login expired" ([release notes](docs/releases/1.39.0.md)). 1.38.0 made closing a session reliable across paired PCs ([notes](docs/releases/1.38.0.md)). See [the maintenance review](docs/quality-review-2026-09-08.md) and [session synchronization review](docs/session-sync-review-2026-09-08.md). +> **1.44.1** fixes the "Assertion failed: remove_pty_baton" dialog (and the occasional hang or crash) when quitting or running Update all with several sessions open ([release notes](docs/releases/1.44.1.md)). 1.44.0 lists Claude Code's own background sessions (`claude --bg`, the agent view) in the sidebar, marks the ones waiting for you, and opens one in a tile by attaching to it ([release notes](docs/releases/1.44.0.md)). 1.43.0 runs parallel sessions in separate workspaces — each in its own git worktree and branch, so two agents never edit the same files — with its changes in the header and a clean-up that warns before unmerged work is lost ([release notes](docs/releases/1.43.0.md)). 1.42.0 shows what changed — a What's new window after each update, with Show me, and NEW dots on new buttons until you try them — and, as an experimental setting on Windows, lets Claude Code report its own state (working, needs approval, asking, done) instead of DevDeck reading the screen ([release notes](docs/releases/1.42.0.md)). 1.41.0 adds quick replies (one click or Ctrl+Shift+1…9 for the phrases you keep typing), a session timeline of your prompts with their times, replies and images, red boxes and arrows drawn on a screenshot and sent straight back to the session, and text, log and PDF attachments for sessions on another PC ([release notes](docs/releases/1.41.0.md)). 1.40.0 ties each screenshot to the terminal row it appeared on — hover a highlighted `[Image]` for a thumbnail, click for the full view, and jump from the viewer back to the row — says what led to each image, previews new screenshots, copies images, and adds Ctrl+↑/↓ between your prompts ([release notes](docs/releases/1.40.0.md)). 1.39.0 shows the screenshots and images a Claude session worked with — Claude Desktop shows them inline; DevDeck puts them in a strip beside the terminal, also for sessions on another PC — adds 📎 / drag-and-drop attachments (images travel to a remote PC), **Update all** for Claude Code on Windows, staggered session restores that no longer stall a PC, and ends the daily false "Login expired" ([release notes](docs/releases/1.39.0.md)). 1.38.0 made closing a session reliable across paired PCs ([notes](docs/releases/1.38.0.md)). See [the maintenance review](docs/quality-review-2026-09-08.md) and [session synchronization review](docs/session-sync-review-2026-09-08.md).
diff --git a/docs/releases/1.44.1.md b/docs/releases/1.44.1.md
new file mode 100644
index 0000000..26f8744
--- /dev/null
+++ b/docs/releases/1.44.1.md
@@ -0,0 +1,15 @@
+# DevDeck 1.44.1
+
+## No more "Assertion failed: remove_pty_baton" on quit or Update all
+
+On Windows, closing DevDeck, or running **Update all**, with two or more sessions open could show a Microsoft Visual C++ Runtime Library dialog: `Assertion failed! … conpty.node … Expression: remove_pty_baton(baton->id)`. Other times DevDeck kept running in the background after its window closed, or exited with a crash.
+
+- **Cause:** the terminal library DevDeck uses (node-pty) keeps its open terminals in one list. When a terminal ends, a background thread removes it from that list, without a lock. Two terminals ending at the same moment edited the list at once, and one of them could no longer find itself. A fix exists upstream only in a beta.
+- **Fix:** DevDeck now ends its terminals one at a time. Each starts ending only after the previous one has reported its exit, by which time its thread is done with the list. This applies wherever terminals are ended: quitting, Update all, closing a group, and closing a single session.
+- **Quitting with many sessions:** this takes up to about 1.5 seconds per session, instead of a fixed 5 seconds. A terminal that never reports its exit holds up the next one for at most 4 seconds.
+- **Measured:** the automated Update-all check stopped or crashed at quit on 3 of 20 and 3 of 32 runs before. After the fix it passed 50 of 50.
+
+## Also
+
+- **Accessibility:** the sidebar's buttons are now named by what they show, in the same order, for voice control (WCAG 2.5.3). The group toggles, session rows and project rows are affected. axe-core 4.14 checks this.
+- **Development:** esbuild 0.28.2, playwright 1.64.1, axe-core 4.14.0. Test runs no longer leave their temp folders behind.
diff --git a/package-lock.json b/package-lock.json
index 97e8900..591306b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "devdeck",
- "version": "1.44.0",
+ "version": "1.44.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "devdeck",
- "version": "1.44.0",
+ "version": "1.44.1",
"license": "MIT",
"dependencies": {
"@fontsource-variable/geist": "5.3.0",
diff --git a/package.json b/package.json
index 5788f79..552abf1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "devdeck",
- "version": "1.44.0",
+ "version": "1.44.1",
"description": "Project command deck — at-a-glance state + Claude Code, Codex, and Antigravity resume",
"main": "dist/main/main.js",
"type": "commonjs",
diff --git a/src/main/ptyHost.test.ts b/src/main/ptyHost.test.ts
index af7f7fc..11c1fa7 100644
--- a/src/main/ptyHost.test.ts
+++ b/src/main/ptyHost.test.ts
@@ -26,14 +26,14 @@ describe('PtyHost', () => {
const work = host.shutdown();
expect(host.shutdown()).toBe(work);
expect(a.kill).toHaveBeenCalledTimes(1);
- expect(b.kill).toHaveBeenCalledTimes(1);
+ expect(b.kill).not.toHaveBeenCalled(); // waits for a to finish exiting
expect(() => host.create('c', 'cmd', [], '.', 80, 24, () => {}, () => {})).toThrow('shutting down');
let finished = false;
void work.then(() => { finished = true; });
- b.emitExit(0);
- await Promise.resolve();
- expect(finished).toBe(false);
a.emitExit(0);
+ await vi.waitFor(() => expect(b.kill).toHaveBeenCalledTimes(1));
+ expect(finished).toBe(false);
+ b.emitExit(0);
await work;
expect(finished).toBe(true);
});
@@ -71,7 +71,7 @@ describe('PtyHost', () => {
expect(onData).toHaveBeenCalledWith('hello');
});
- it('write/resize/kill delegate to the right process; killAll kills everything', () => {
+ it('write/resize/kill delegate to the right process; killAll kills everything', async () => {
const a = fakeProc(), b = fakeProc();
const spawn = vi.fn().mockReturnValueOnce(a).mockReturnValueOnce(b) as unknown as PtySpawn;
const host = new PtyHost(spawn);
@@ -80,7 +80,29 @@ describe('PtyHost', () => {
host.write('a', 'x'); expect(a.write).toHaveBeenCalledWith('x');
host.resize('b', 120, 40); expect(b.resize).toHaveBeenCalledWith(120, 40);
host.kill('a'); expect(a.kill).toHaveBeenCalled();
- host.killAll(); expect(b.kill).toHaveBeenCalled();
+ host.killAll(); expect(b.kill).not.toHaveBeenCalled();
+ a.emitExit(0);
+ await vi.waitFor(() => expect(b.kill).toHaveBeenCalled());
+ });
+
+ it('ends terminals one at a time, each after the previous exit (node-pty races two exits on Windows)', async () => {
+ vi.useFakeTimers();
+ try {
+ const procs = [fakeProc(), fakeProc(), fakeProc()];
+ const spawn = vi.fn();
+ for (const p of procs) spawn.mockReturnValueOnce(p);
+ const host = new PtyHost(spawn as unknown as PtySpawn);
+ for (const id of ['a', 'b', 'c']) host.create(id, 'pwsh', [], '.', 80, 24, () => {}, () => {});
+ host.killAll();
+ const killed = () => procs.map((p) => (p.kill as ReturnType