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
7 changes: 7 additions & 0 deletions packages/happy-cli/src/claude/sdk/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ export function getCleanEnv(): NodeJS.ProcessEnv {
logger.debug('[Claude SDK] Removed Bun-specific environment variables for Node.js compatibility')
}

// Remove Claude Code nested-session detection variables.
// When the daemon is started from within a Claude Code session, CLAUDECODE=1 leaks
// into all child processes. Claude Code detects this and refuses to start, causing
// every remote session to crash with "Process exited unexpectedly".
delete env.CLAUDECODE
delete env.CLAUDE_CODE_ENTRYPOINT

return env
}

Expand Down
21 changes: 17 additions & 4 deletions packages/happy-cli/src/daemon/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,11 @@ export async function startDaemon(): Promise<void> {
// Add extra environment variables (these should already be filtered)
Object.assign(tmuxEnv, extraEnv);

// Remove Claude Code nested-session detection variables so spawned
// sessions don't inherit them from a daemon started inside Claude Code.
delete tmuxEnv.CLAUDECODE;
delete tmuxEnv.CLAUDE_CODE_ENTRYPOINT;

const tmuxResult = await tmux.spawnInTmux([fullCommand], {
sessionName: tmuxSessionName,
windowName: windowName,
Expand Down Expand Up @@ -497,14 +502,22 @@ export async function startDaemon(): Promise<void> {

// TODO: In future, sessionId could be used with --resume to continue existing sessions
// For now, we ignore it - each spawn creates a new session
// Build a clean environment for the spawned session.
// Remove CLAUDECODE and CLAUDE_CODE_ENTRYPOINT to prevent Claude Code from
// detecting a "nested session" and refusing to start when the daemon was
// launched from within a Claude Code session.
const spawnEnv: Record<string, string | undefined> = {
...process.env,
...extraEnv
};
delete spawnEnv.CLAUDECODE;
delete spawnEnv.CLAUDE_CODE_ENTRYPOINT;

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,
...extraEnv
}
env: spawnEnv
});

// Log output for debugging
Expand Down
8 changes: 5 additions & 3 deletions packages/happy-cli/src/ui/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,11 @@ class Logger {
}

private logToFile(prefix: string, message: string, ...args: unknown[]): void {
const logLine = `${prefix} ${message} ${args.map(arg =>
typeof arg === 'string' ? arg : JSON.stringify(arg)
).join(' ')}\n`
const logLine = `${prefix} ${message} ${args.map(arg => {
if (typeof arg === 'string') return arg
if (arg instanceof Error) return arg.stack || arg.message
return JSON.stringify(arg)
}).join(' ')}\n`

// Send to remote server if configured
if (this.dangerouslyUnencryptedServerLoggingUrl) {
Expand Down