slopus/happy · error
Session environment is invalid - environment variables not f
Error message
Session environment is invalid - environment variables not found in daemon: ${unresolvedEnvEntries.join('; ')}. Ensure these variables are set in the daemon's environment before starting sessions. What it means
When the daemon's spawnSession RPC prepares a session's environment, it expands `${VAR}` references against the daemon's own (ambient) environment. Afterwards it scans every value for leftover `${...}` patterns; if any remain — meaning a referenced variable was never defined in the daemon's environment — the spawn is rejected with a typed error result listing each unresolved reference, and no session process is started.
Source
Thrown at packages/happy-cli/src/daemon/run.ts:391
const unresolvedMatch = value.match(/\$\{([^}]+)\}/);
if (!unresolvedMatch) {
return [];
}
const expression = unresolvedMatch[1];
const defaultSeparatorIndex = expression.indexOf(':-');
const missingVar = defaultSeparatorIndex === -1
? expression
: expression.slice(0, defaultSeparatorIndex);
return [`${key} references \${${missingVar}} which is not defined`];
});
if (unresolvedEnvEntries.length > 0) {
const errorMessage = `Session environment is invalid - environment variables not found in daemon: ${unresolvedEnvEntries.join('; ')}. ` +
`Ensure these variables are set in the daemon's environment before starting sessions.`;
logger.warn(`[DAEMON RUN] ${errorMessage}`);
return {
type: 'error',
errorMessage
};
}
// Check if tmux is available and should be used
const tmuxAvailable = await isTmuxAvailable();
let useTmux = tmuxAvailable;
// Get tmux session name from environment variables (now set by profile system)
// Empty string means "use current/most recent session" (tmux default behavior)
let tmuxSessionName: string | undefined = extraEnv.TMUX_SESSION_NAME;
// If tmux is not available or session name is explicitly undefined, fall back to regular spawning
// Note: Empty string is valid (means use current/most recent tmux session)
if (!tmuxAvailable || tmuxSessionName === undefined) {
useTmux = false;View on GitHub (pinned to b824cd0a46)
Solutions
- Export the missing variable in the daemon's environment: stop the daemon (`happy daemon stop`) and start it from a shell where the variable is set, e.g. `Z_AI_AUTH_TOKEN=... happy daemon start`.
- Read the error message — it names each `${VAR}` that is undefined — and add those exact variables to the daemon's profile/launch environment.
- If the daemon is OS-managed (launchd/systemd), put the vars in the service's environment file (launchctl setenv / EnvironmentFile=) rather than shell rc files.
- Alternatively remove or inline the `${VAR}` reference from the session's environmentVariables configuration so no expansion is needed.
Example fix
// before: daemon started without the referenced var
export ANTHROPIC_AUTH_TOKEN="${Z_AI_AUTH_TOKEN}" // Z_AI_AUTH_TOKEN undefined in daemon → error
// after: start the daemon with the var present
Z_AI_AUTH_TOKEN=sk-real-key happy daemon stop && Z_AI_AUTH_TOKEN=sk-real-key happy daemon start Defensive patterns
Strategy: validation
Validate before calling
// Validate session env vars are resolvable in the daemon's environment before spawning
function hasUnresolvedRefs(env: Record<string, string>): string[] {
return Object.entries(env)
.filter(([, v]) => typeof v === 'string' && v.includes('${'))
.map(([k, v]) => `${k}=${v}`);
}
const unresolved = hasUnresolvedRefs(sessionOptions.environmentVariables ?? {});
if (unresolved.length > 0) {
// Also check each referenced name exists in the daemon process
for (const entry of unresolved) {
const m = entry.match(/\$\{([^}:-]+)/);
if (m && !(m[1] in process.env)) {
throw new Error(`Define ${m[1]} in the daemon environment before spawning: missing for ${entry}`);
}
}
} Type guard
function envRefIsResolvable(value: string, daemonEnv: NodeJS.ProcessEnv): boolean {
const m = value.match(/\$\{([^}:-]+)(?::-[^}]*)?\}/);
return m === null || daemonEnv[m[1]] !== undefined;
} Try / catch
const result = await daemonRpc('spawn-happy-session', options);
if (result.type === 'error') {
// message lists each `${VAR}` missing from the daemon env
console.error(result.errorMessage);
// fix: export the named vars where the daemon runs, or inline values, then retry
} Prevention
- Start the daemon from a shell that has all needed API keys exported, or set them in the OS service definition (launchctl setenv / systemd EnvironmentFile).
- Remember shell rc files (~/.zshrc, ~/.bashrc) are NOT loaded by the detached daemon.
- Keep session environment templates in sync with actual variable names; remove stale ${VAR} references after renaming keys.
- Test with `happy daemon status` + a trivial session spawn after changing daemon environment.
When it happens
Trigger: A session's environmentVariables map contains a value like `"ANTHROPIC_AUTH_TOKEN": "${Z_AI_AUTH_TOKEN}"` but Z_AI_AUTH_TOKEN is not set in the daemon process's environment (it was set only in the interactive shell that launched previous sessions, or in a shell rc file the detached daemon never sources).
Common situations: API keys exported in ~/.zshrc but the daemon runs detached via launchd/systemd or `happy daemon start` from a different shell; user renames an env var in their profile but session templates still reference the old name; tmux vs non-tmux environments diverging; vars set with `sudo` or in a container not visible to the daemon.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- [EXPAND ENV] Session may fail to authenticate. Set these in
- Daemon-spawned sessions cannot use local/interactive mode. U
- [EXPAND ENV] WARNING: ${varName} is set but EMPTY in daemon
- No machine ID found in settings
- Unknown agent: ${id}. Available agents: ${available}
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/d4d137c843f453f1.
Report an issue: GitHub.