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

  1. 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`.
  2. Read the error message — it names each `${VAR}` that is undefined — and add those exact variables to the daemon's profile/launch environment.
  3. 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.
  4. 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

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


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/d4d137c843f453f1. Report an issue: GitHub.