Skip to content
Open
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
10 changes: 7 additions & 3 deletions packages/happy-cli/src/daemon/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,14 +495,18 @@ export async function startDaemon(): Promise<void> {
'--started-by', 'daemon'
];

// TODO: In future, sessionId could be used with --resume to continue existing sessions
// For now, we ignore it - each spawn creates a new session
// Strip CLAUDECODE from env — if the daemon was started from
// within a Claude Code session, child processes inherit it and
// Claude refuses to launch ("cannot be launched inside another
// Claude Code session").
const { CLAUDECODE: _stripped, ...cleanEnv } = process.env;

const happyProcess = spawnHappyCLI(args, {
cwd: directory,
detached: true, // Sessions stay alive when daemon stops
stdio: ['ignore', 'pipe', 'pipe'], // Capture stdout/stderr for debugging
env: {
...process.env,
...cleanEnv,
...extraEnv
}
});
Expand Down
64 changes: 64 additions & 0 deletions packages/happy-cli/src/daemon/stripClaudeCodeEnv.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Tests for CLAUDECODE environment variable stripping (regression #682).
*
* When the daemon is started from within a Claude Code session, the
* CLAUDECODE=1 env var is inherited. If passed to child session processes,
* Claude Code refuses to launch with "cannot be launched inside another
* Claude Code session". The daemon must strip it before spawning.
*/

import { describe, it, expect } from 'vitest';

describe('CLAUDECODE env stripping (issue #682)', () => {
it('should remove CLAUDECODE from env while preserving other vars', () => {
const env: Record<string, string> = {
HOME: '/home/user',
PATH: '/usr/bin',
CLAUDECODE: '1',
NODE_ENV: 'production',
};

// Same destructuring pattern used in daemon/run.ts
const { CLAUDECODE: _stripped, ...cleanEnv } = env;

expect(cleanEnv).toEqual({
HOME: '/home/user',
PATH: '/usr/bin',
NODE_ENV: 'production',
});
expect('CLAUDECODE' in cleanEnv).toBe(false);
});

it('should work when CLAUDECODE is not present', () => {
const env: Record<string, string> = {
HOME: '/home/user',
PATH: '/usr/bin',
};

const { CLAUDECODE: _stripped, ...cleanEnv } = env;

expect(cleanEnv).toEqual({
HOME: '/home/user',
PATH: '/usr/bin',
});
});

it('should allow extraEnv to override cleaned env', () => {
const env: Record<string, string> = {
HOME: '/home/user',
CLAUDECODE: '1',
EXISTING_VAR: 'old',
};
const extraEnv: Record<string, string> = {
EXISTING_VAR: 'new',
NEW_VAR: 'value',
};

const { CLAUDECODE: _stripped, ...cleanEnv } = env;
const finalEnv = { ...cleanEnv, ...extraEnv };

expect('CLAUDECODE' in finalEnv).toBe(false);
expect(finalEnv.EXISTING_VAR).toBe('new');
expect(finalEnv.NEW_VAR).toBe('value');
});
});