From 4382b377573fb2edbfa42babeb555cda9d7d301a Mon Sep 17 00:00:00 2001 From: Enwei Jiao Date: Mon, 19 Oct 2026 14:40:01 +0800 Subject: [PATCH 1/3] fix(server): recognise the native Claude Code build by its process shape The native build runs with its version as the process name and titles itself "Claude" (argv ["Claude"]), which the lowercase executable match missed, so its panes showed terminal text instead of the conversation. Only that exact shape is accepted; POSIX names otherwise keep their case. --- changelog.d/claude-native-build.md | 2 ++ server/claude-store.test.ts | 7 +++++++ server/claude-store.ts | 4 +++- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changelog.d/claude-native-build.md diff --git a/changelog.d/claude-native-build.md b/changelog.d/claude-native-build.md new file mode 100644 index 0000000000..e3213eac00 --- /dev/null +++ b/changelog.d/claude-native-build.md @@ -0,0 +1,2 @@ +### Fixed +- Panes running the native Claude Code build show the conversation instead of terminal text. diff --git a/server/claude-store.test.ts b/server/claude-store.test.ts index 876bac9914..ca0f926362 100644 --- a/server/claude-store.test.ts +++ b/server/claude-store.test.ts @@ -8,6 +8,13 @@ import type { ProcessRow } from "./windows-processes.ts"; const NATIVE = process.platform === "linux" || process.platform === "darwin"; const SESSION = "0b8e6f0e-8d3f-4c1a-9a53-6c2b7a1d9e42"; +describe("isClaudeProcess", () => { + it("knows the native build, whose process is titled Claude", () => { + expect(isClaudeProcess({ name: "2.1.302", argv0: "Claude", argv: ["Claude"] })).toBe(true); + expect(isClaudeProcess({ name: "Claude Helper", argv: ["/Applications/Claude.app/Contents/MacOS/Claude Helper"] })).toBe(false); + }); +}); + describe("claudeProcessSession", () => { const roots: string[] = []; afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); }); diff --git a/server/claude-store.ts b/server/claude-store.ts index 395628cce7..cf75b44e15 100644 --- a/server/claude-store.ts +++ b/server/claude-store.ts @@ -32,7 +32,9 @@ function isClaudeExecutable(text = ""): boolean { /** A Claude Code process in herdr's process info. */ export function isClaudeProcess(entry: { name?: string; argv0?: string; argv?: readonly string[] }): boolean { // macOS keeps the executable name "node" for npm installs; the process title is argv0. - return isClaudeExecutable(entry.name) || isClaudeExecutable(entry.argv0) || isClaudeExecutable(entry.argv?.[0]); + if (isClaudeExecutable(entry.name) || isClaudeExecutable(entry.argv0) || isClaudeExecutable(entry.argv?.[0])) return true; + // the native build runs under its version as the name and titles itself "Claude" + return /^\d+\.\d+\.\d+$/.test(entry.name ?? "") && (entry.argv0 === "Claude" || entry.argv?.[0] === "Claude"); } /** Java's String.hashCode, which Claude Code uses for the suffix of a long name. */ From 9e68f6ad8354df45eadbcc0a06c1583419fddc0f Mon Sep 17 00:00:00 2001 From: Enwei Jiao Date: Wed, 21 Oct 2026 17:38:16 +0800 Subject: [PATCH 2/3] fix(server): take the native Claude Code shape only when both its argv0 and argv[0] say Claude One of the two fields alone is not that shape and could take an unrelated process for Claude Code. Tests cover each field alone, and the native build launched as lowercase `claude`, which the executable match already takes. --- server/claude-store.test.ts | 5 +++++ server/claude-store.ts | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/server/claude-store.test.ts b/server/claude-store.test.ts index ca0f926362..bcbbfe4505 100644 --- a/server/claude-store.test.ts +++ b/server/claude-store.test.ts @@ -12,6 +12,11 @@ describe("isClaudeProcess", () => { it("knows the native build, whose process is titled Claude", () => { expect(isClaudeProcess({ name: "2.1.302", argv0: "Claude", argv: ["Claude"] })).toBe(true); expect(isClaudeProcess({ name: "Claude Helper", argv: ["/Applications/Claude.app/Contents/MacOS/Claude Helper"] })).toBe(false); + // one field alone is not that shape + expect(isClaudeProcess({ name: "2.1.302", argv0: "Claude", argv: ["Other"] })).toBe(false); + expect(isClaudeProcess({ name: "2.1.302", argv0: "Other", argv: ["Claude"] })).toBe(false); + // the native build as herdr reports it run from its lowercase `claude` launcher: the executable match + expect(isClaudeProcess({ name: "2.1.306", argv0: "claude", argv: ["claude"] })).toBe(true); }); }); diff --git a/server/claude-store.ts b/server/claude-store.ts index cf75b44e15..3ee616c01b 100644 --- a/server/claude-store.ts +++ b/server/claude-store.ts @@ -33,8 +33,9 @@ function isClaudeExecutable(text = ""): boolean { export function isClaudeProcess(entry: { name?: string; argv0?: string; argv?: readonly string[] }): boolean { // macOS keeps the executable name "node" for npm installs; the process title is argv0. if (isClaudeExecutable(entry.name) || isClaudeExecutable(entry.argv0) || isClaudeExecutable(entry.argv?.[0])) return true; - // the native build runs under its version as the name and titles itself "Claude" - return /^\d+\.\d+\.\d+$/.test(entry.name ?? "") && (entry.argv0 === "Claude" || entry.argv?.[0] === "Claude"); + // the native build runs under its version as the name and titles itself "Claude": both its + // argv0 and its argv[0] say so, which one field alone does not prove + return /^\d+\.\d+\.\d+$/.test(entry.name ?? "") && entry.argv0 === "Claude" && entry.argv?.[0] === "Claude"; } /** Java's String.hashCode, which Claude Code uses for the suffix of a long name. */ From 02a30477c9d0a1da0c34d17140100ab62d13cf75 Mon Sep 17 00:00:00 2001 From: Hako Date: Thu, 22 Oct 2026 21:10:02 +0900 Subject: [PATCH 3/3] test(server): record the native Claude shape seen on a case-insensitive Mac disk On macOS 27 (case-insensitive APFS), typing `Claude` finds the ~/.local/bin/claude link: herdr's name is the versioned binary behind it and argv0/argv[0] keep `Claude`. Add that recorded shape as a test case and narrow the changelog entry to it, since a native build started as `claude` was already recognised. Co-Authored-By: Claude Opus 5.5 --- changelog.d/claude-native-build.md | 3 ++- server/claude-store.test.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/changelog.d/claude-native-build.md b/changelog.d/claude-native-build.md index e3213eac00..193991e613 100644 --- a/changelog.d/claude-native-build.md +++ b/changelog.d/claude-native-build.md @@ -1,2 +1,3 @@ ### Fixed -- Panes running the native Claude Code build show the conversation instead of terminal text. +- A Claude Code pane on macOS that was started as `Claude` (capital C) shows the conversation + instead of terminal text. diff --git a/server/claude-store.test.ts b/server/claude-store.test.ts index bcbbfe4505..3918bbf7e9 100644 --- a/server/claude-store.test.ts +++ b/server/claude-store.test.ts @@ -18,6 +18,12 @@ describe("isClaudeProcess", () => { // the native build as herdr reports it run from its lowercase `claude` launcher: the executable match expect(isClaudeProcess({ name: "2.1.306", argv0: "claude", argv: ["claude"] })).toBe(true); }); + + it("knows the native build typed as Claude on a case-insensitive Mac disk", () => { + // recorded on macOS 27 (case-insensitive APFS): `Claude` finds the ~/.local/bin/claude link, so + // herdr's name is the versioned binary behind it and argv0/argv[0] keep the typed `Claude` + expect(isClaudeProcess({ name: "2.1.999", argv0: "Claude", argv: ["Claude", "42"] })).toBe(true); + }); }); describe("claudeProcessSession", () => {