From 1b354968144d0a676fef96a3fcd8c5d3cc50a7bf Mon Sep 17 00:00:00 2001 From: gfbonny Date: Thu, 26 Feb 2026 22:06:07 -0800 Subject: [PATCH 1/2] fix: strip server env vars (PORT, AUTH_TOKEN, etc.) from child PTY processes Child terminals inherited Freshell's PORT, VITE_PORT, AUTH_TOKEN, and ALLOWED_ORIGINS, causing port conflicts when running other dev servers inside Freshell and leaking credentials into child processes. Co-Authored-By: Claude Opus 4.6 --- server/terminal-registry.ts | 6 ++++++ test/unit/server/terminal-env.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/server/terminal-registry.ts b/server/terminal-registry.ts index 5fbf1191..7be0a5ac 100644 --- a/server/terminal-registry.ts +++ b/server/terminal-registry.ts @@ -630,12 +630,18 @@ export function buildSpawnSpec( // Strip inherited env vars that interfere with child terminal behaviour: // - CLAUDECODE: causes child Claude processes to refuse to start ("nested session" error) // - CI/NO_COLOR/FORCE_COLOR/COLOR: disables interactive color in user PTYs + // - PORT/VITE_PORT/AUTH_TOKEN/ALLOWED_ORIGINS: server-specific vars that cause + // port conflicts and leak credentials into child processes const { CLAUDECODE: _claudecode, CI: _ci, NO_COLOR: _noColor, FORCE_COLOR: _forceColor, COLOR: _color, + PORT: _port, + VITE_PORT: _vitePort, + AUTH_TOKEN: _authToken, + ALLOWED_ORIGINS: _allowedOrigins, ...parentEnv } = process.env const env = { diff --git a/test/unit/server/terminal-env.test.ts b/test/unit/server/terminal-env.test.ts index 68ea0cfd..a34e8c8e 100644 --- a/test/unit/server/terminal-env.test.ts +++ b/test/unit/server/terminal-env.test.ts @@ -59,4 +59,26 @@ describe('TerminalRegistry env injection', () => { expect(env.FRESHELL_TAB_ID).toBe('tab_x') expect(env.FRESHELL_PANE_ID).toBe('pane_y') }) + + it('strips server-specific env vars from child PTY processes', () => { + process.env.PORT = '3001' + process.env.VITE_PORT = '5173' + process.env.AUTH_TOKEN = 'secret-token' + process.env.ALLOWED_ORIGINS = 'http://localhost:3001' + // Sanity: keep a non-stripped var to confirm env is still passed + process.env.HOME = '/home/test' + + const registry = new TerminalRegistry() + registry.create({ mode: 'shell', envContext: { tabId: 'tab_1', paneId: 'pane_1' } }) + + const call = vi.mocked(pty.spawn).mock.calls[0] + const env = call[2].env as Record + + expect(env.PORT).toBeUndefined() + expect(env.VITE_PORT).toBeUndefined() + expect(env.AUTH_TOKEN).toBeUndefined() + expect(env.ALLOWED_ORIGINS).toBeUndefined() + // Non-stripped vars still pass through + expect(env.HOME).toBe('/home/test') + }) }) From a02f376fb32b28a98a5e9b857e644aabd416b2ef Mon Sep 17 00:00:00 2001 From: gfbonny Date: Thu, 26 Feb 2026 22:08:21 -0800 Subject: [PATCH 2/2] docs: update AGENTS.md architecture and add CLAUDE.md header Refresh directory structure, add testing/linting command docs, and update architectural descriptions to reflect current codebase layout. Co-Authored-By: Claude Opus 4.6 --- AGENTS.md | 44 +++++++++++++++++++++++++++++++++++++------- CLAUDE.md | 6 +++++- package-lock.json | 42 +++++++++++------------------------------- 3 files changed, 53 insertions(+), 39 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a30b518f..15f2deb8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,6 +74,22 @@ npm test # Run all tests (client + server) npm run check # Typecheck + test without building (safe while prod runs) npm run verify # Build + test (catches type errors that vitest misses) npm run test:coverage # Generate coverage report + +# Run a single test file +npx vitest run test/unit/client/components/TabBar.test.tsx +# Server tests use a separate config +npx vitest run --config vitest.server.config.ts test/server/ws-protocol.test.ts +# Run tests matching a name pattern +npx vitest run -t "handles resize" +``` + +There are **two vitest configs**: `vitest.config.ts` (client, jsdom environment) and `vitest.server.config.ts` (server, node environment). Client tests live in `test/unit/client/`, `test/integration/client/`, and `test/e2e/`. Server tests live in `test/server/`, `test/unit/server/`, and `test/integration/server/`. The default `npm test` runs the client config; `npm run test:all` runs both. + +### Linting +```bash +npm run lint # ESLint on src/ (includes jsx-a11y) +npm run lint:fix # Auto-fix lint issues +npm run typecheck # Typecheck client + server ``` ## Architecture @@ -85,16 +101,23 @@ npm run test:coverage # Generate coverage report ### Directory Structure - `src/` - React frontend application - - `components/` - UI components (TabBar, Sidebar, TerminalView, HistoryView, etc.) - - `store/` - Redux slices (tabs, connection, sessions, settings, claude) - - `lib/` - Utilities (api.ts, claude-types.ts) + - `components/` - UI components (TabBar, Sidebar, TerminalView, panes/, claude-chat/, context-menu/, etc.) + - `store/` - Redux slices, middleware, selectors, and persistence + - `hooks/` - Custom React hooks (activity monitor, notifications, mobile, theme) + - `lib/` - Client utilities (ws-client.ts, api.ts, pane-utils.ts, terminal helpers) - `server/` - Node.js/Express backend - - `index.ts` - HTTP/REST routes and server entry + - `*-router.ts` - Express route modules (terminals, sessions, settings, ai, debug, files, etc.) - `ws-handler.ts` - WebSocket protocol handler - `terminal-registry.ts` - PTY lifecycle management - - `claude-session.ts` - Claude session discovery & indexing - - `claude-indexer.ts` - File watcher for ~/.claude directory -- `test/` - Test suites organized by unit/integration and client/server + - `coding-cli/` - Coding CLI provider abstraction (Claude, Codex) with session indexer and manager + - `session-scanner/` - File watcher service for session discovery and caching + - `sessions-sync/` - Delta sync for session updates to clients + - `tabs-registry/` - Persistent tab/pane state store + - `updater/` - Auto-update version checking +- `shared/` - Code shared between client and server + - `ws-protocol.ts` - Single source of truth for WebSocket message schemas (Zod) + - `path-utils.ts` - Cross-platform path utilities +- `test/` - Tests organized by scope: `unit/client/`, `unit/server/`, `integration/server/`, `integration/client/`, `server/`, `e2e/` ### Key Architectural Patterns @@ -146,3 +169,10 @@ All components **must** be accessible for browser-use automation and WCAG compli - `@/` → `src/` - `@test/` → `test/` +- `@shared/` → `shared/` + +## TypeScript Configs + +Two separate configs reflect the client/server split: +- `tsconfig.json` — Client (Bundler resolution, ESNext modules, jsx). Includes `src/` and `shared/`. +- `tsconfig.server.json` — Server (NodeNext resolution, ESM). Server imports must use `.js` extensions. diff --git a/CLAUDE.md b/CLAUDE.md index eef4bd20..078c29c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,5 @@ -@AGENTS.md \ No newline at end of file +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +@AGENTS.md diff --git a/package-lock.json b/package-lock.json index a3cde634..0e9b7389 100644 --- a/package-lock.json +++ b/package-lock.json @@ -243,7 +243,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -593,7 +592,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -617,7 +615,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -639,7 +636,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2332,7 +2328,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2465,7 +2462,6 @@ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -2573,7 +2569,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2585,7 +2580,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -2714,7 +2708,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -3180,7 +3173,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3731,7 +3723,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4564,7 +4555,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dotenv": { "version": "16.6.1", @@ -4901,7 +4893,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6749,7 +6740,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -6789,7 +6779,6 @@ "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.1.0", "data-urls": "^5.0.0", @@ -7044,6 +7033,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -8042,8 +8032,7 @@ "version": "0.52.2", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ms": { "version": "2.1.3", @@ -8619,7 +8608,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8798,6 +8786,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -8813,6 +8802,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -8980,7 +8970,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8993,7 +8982,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -9007,7 +8995,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-markdown": { "version": "9.1.0", @@ -9041,7 +9030,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -9146,8 +9134,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -10314,7 +10301,6 @@ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -10456,7 +10442,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -10623,7 +10608,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -10748,7 +10732,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10997,7 +10980,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -11598,7 +11580,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12025,7 +12006,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }