mirror of
https://github.com/letta-ai/claude-subconscious.git
synced 2026-04-25 17:04:56 +02:00
fix: Windows TTY crash and LETTA_HOME shell expansion
#47: Skip /dev/tty entirely on Windows — it resolves to C:\dev\tty which doesn't exist. The .on('error') handler from v2.1.1 catches the async error on Linux, but on Windows we shouldn't even attempt it. #48: Expand common shell syntax ($HOME, ${HOME}, ~) in LETTA_HOME. When set via Claude Code settings.json, env vars aren't shell-expanded, so "$HOME" becomes a literal directory name. expandPath() now resolves these to os.homedir(). Fixes both getDurableStateDir() call sites and the splash screen display. Fixes #47, fixes #48. Written by Cameron ◯ Letta Code "Defensive programming is the art of expecting the unexpected." - Unknown
This commit is contained in:
@@ -115,12 +115,27 @@ export type LogFn = (message: string) => void;
|
||||
// Default no-op logger
|
||||
const noopLog: LogFn = () => {};
|
||||
|
||||
/**
|
||||
* Expand common shell syntax in a path value.
|
||||
* Handles $HOME, ${HOME}, and ~ when set via settings.json (no shell expansion).
|
||||
*/
|
||||
export function expandPath(value: string): string {
|
||||
const home = os.homedir();
|
||||
if (value === '$HOME' || value === '${HOME}') return home;
|
||||
if (value.startsWith('$HOME/')) return path.join(home, value.slice(6));
|
||||
if (value.startsWith('${HOME}/')) return path.join(home, value.slice(8));
|
||||
if (value === '~') return home;
|
||||
if (value.startsWith('~/')) return path.join(home, value.slice(2));
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get durable state directory path
|
||||
* If LETTA_HOME is set, use that as the base instead of cwd
|
||||
*/
|
||||
export function getDurableStateDir(cwd: string): string {
|
||||
const base = process.env.LETTA_HOME || cwd;
|
||||
const raw = process.env.LETTA_HOME || cwd;
|
||||
const base = process.env.LETTA_HOME ? expandPath(raw) : raw;
|
||||
return path.join(base, '.letta', 'claude');
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
getMode,
|
||||
getTempStateDir,
|
||||
getSdkToolsMode,
|
||||
expandPath,
|
||||
} from './conversation_utils.js';
|
||||
import { buildLettaApiUrl } from './letta_api_url.js';
|
||||
|
||||
@@ -64,7 +65,8 @@ interface Conversation {
|
||||
// Durable storage in .letta directory
|
||||
// If LETTA_HOME is set, use that as the base instead of cwd
|
||||
function getDurableStateDir(cwd: string): string {
|
||||
const base = process.env.LETTA_HOME || cwd;
|
||||
const raw = process.env.LETTA_HOME || cwd;
|
||||
const base = process.env.LETTA_HOME ? expandPath(raw) : raw;
|
||||
return path.join(base, '.letta', 'claude');
|
||||
}
|
||||
|
||||
@@ -253,12 +255,15 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
// Try to open TTY for user-visible output (bypasses Claude's capture)
|
||||
// Skip on Windows — /dev/tty resolves to C:\dev\tty which doesn't exist
|
||||
let tty: fs.WriteStream | null = null;
|
||||
try {
|
||||
tty = fs.createWriteStream('/dev/tty');
|
||||
tty.on('error', () => { tty = null; }); // Handle async ENXIO when /dev/tty unavailable
|
||||
} catch {
|
||||
// TTY not available (e.g., non-interactive session)
|
||||
if (process.platform !== 'win32') {
|
||||
try {
|
||||
tty = fs.createWriteStream('/dev/tty');
|
||||
tty.on('error', () => { tty = null; }); // Handle async ENXIO when /dev/tty unavailable
|
||||
} catch {
|
||||
// TTY not available (e.g., non-interactive session)
|
||||
}
|
||||
}
|
||||
|
||||
const writeTty = (text: string) => {
|
||||
@@ -305,7 +310,7 @@ async function main(): Promise<void> {
|
||||
writeTty(` Server: ${baseUrl}\n`);
|
||||
}
|
||||
if (process.env.LETTA_HOME) {
|
||||
writeTty(` Home: ${process.env.LETTA_HOME}\n`);
|
||||
writeTty(` Home: ${expandPath(process.env.LETTA_HOME)}\n`);
|
||||
}
|
||||
writeTty('\n');
|
||||
writeTty(' Learn about configuration settings:\n');
|
||||
|
||||
Reference in New Issue
Block a user