Summary
In the Art skill, loadEnv() in LifeOS/install/skills/Art/Tools/Generate.ts looks for .env at ${LIFEOS_DIR}/.env. When LIFEOS_DIR is set to the LIFEOS subdirectory ($HOME/.claude/LIFEOS) — which is how many installs configure it — it resolves to $HOME/.claude/LIFEOS/.env, a file that does not exist. The canonical .env lives one level up at $HOME/.claude/.env.
The catch block silently swallows the resulting ENOENT, so no keys are loaded and generation fails with:
❌ Error: Missing environment variable: GOOGLE_API_KEY
…even though the key is present in the real .env. The fallback to $HOME/.claude/.env only fires when LIFEOS_DIR is unset, not when it points at a subdirectory that has no .env.
Location
LifeOS/install/skills/Art/Tools/Generate.ts, loadEnv() (around lines 40–42):
const paiDir = process.env.LIFEOS_DIR || resolve(process.env.HOME!, '.claude');
const envPath = resolve(paiDir, '.env');
Repro
export LIFEOS_DIR="$HOME/.claude/LIFEOS" (a subdirectory, as configured on many installs).
- Put
GOOGLE_API_KEY=... in $HOME/.claude/.env (the canonical location).
- Run the Art generator with a Google model, e.g.
--model nano-banana-pro.
Result: Missing environment variable: GOOGLE_API_KEY, because loadEnv() read $HOME/.claude/LIFEOS/.env (absent) and silently gave up.
Root cause
loadEnv() assumes the .env sits inside LIFEOS_DIR, but the canonical .env is at $HOME/.claude/.env — the parent of the LIFEOS subdirectory. A single hard-coded path plus a silent catch means a present, valid key is invisible to the tool.
Suggested fix
Try multiple candidate paths and load the first that exists (canonical $HOME/.claude/.env included), instead of a single LIFEOS_DIR-relative path:
async function loadEnv(): Promise<void> {
const home = process.env.HOME!;
// The canonical .env is at ~/.claude/.env; LIFEOS_DIR may point at the
// ~/.claude/LIFEOS subdir, so try both. Load the first candidate that exists.
const candidates = Array.from(new Set(
[
process.env.LIFEOS_DIR ? resolve(process.env.LIFEOS_DIR, '.env') : null,
resolve(home, '.claude', '.env'),
].filter((p): p is string => Boolean(p)),
));
for (const envPath of candidates) {
let envContent: string;
try {
envContent = await readFile(envPath, 'utf-8');
} catch {
continue; // not here — try the next candidate
}
for (const line of envContent.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eqIndex = trimmed.indexOf('=');
if (eqIndex === -1) continue;
const key = trimmed.slice(0, eqIndex).trim();
let value = trimmed.slice(eqIndex + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
if (!process.env[key]) process.env[key] = value; // shell still wins
}
}
// …existing alias handling unchanged…
}
This keeps the LIFEOS_DIR-relative path working, adds the canonical $HOME/.claude/.env as a fallback, and preserves the "shell env overrides file" semantics.
Note
The same LIFEOS_DIR-relative .env assumption likely recurs in sibling tools that ship their own loadEnv() (e.g. GenerateMidjourneyImage.ts); worth auditing them together. A shared helper would prevent drift.
Summary
In the Art skill,
loadEnv()inLifeOS/install/skills/Art/Tools/Generate.tslooks for.envat${LIFEOS_DIR}/.env. WhenLIFEOS_DIRis set to the LIFEOS subdirectory ($HOME/.claude/LIFEOS) — which is how many installs configure it — it resolves to$HOME/.claude/LIFEOS/.env, a file that does not exist. The canonical.envlives one level up at$HOME/.claude/.env.The
catchblock silently swallows the resultingENOENT, so no keys are loaded and generation fails with:…even though the key is present in the real
.env. The fallback to$HOME/.claude/.envonly fires whenLIFEOS_DIRis unset, not when it points at a subdirectory that has no.env.Location
LifeOS/install/skills/Art/Tools/Generate.ts,loadEnv()(around lines 40–42):Repro
export LIFEOS_DIR="$HOME/.claude/LIFEOS"(a subdirectory, as configured on many installs).GOOGLE_API_KEY=...in$HOME/.claude/.env(the canonical location).--model nano-banana-pro.Result:
Missing environment variable: GOOGLE_API_KEY, becauseloadEnv()read$HOME/.claude/LIFEOS/.env(absent) and silently gave up.Root cause
loadEnv()assumes the.envsits insideLIFEOS_DIR, but the canonical.envis at$HOME/.claude/.env— the parent of the LIFEOS subdirectory. A single hard-coded path plus a silentcatchmeans a present, valid key is invisible to the tool.Suggested fix
Try multiple candidate paths and load the first that exists (canonical
$HOME/.claude/.envincluded), instead of a singleLIFEOS_DIR-relative path:This keeps the
LIFEOS_DIR-relative path working, adds the canonical$HOME/.claude/.envas a fallback, and preserves the "shell env overrides file" semantics.Note
The same
LIFEOS_DIR-relative.envassumption likely recurs in sibling tools that ship their ownloadEnv()(e.g.GenerateMidjourneyImage.ts); worth auditing them together. A shared helper would prevent drift.