1313 * Port: random 10000-60000 (or BROWSE_PORT env for debug override)
1414 */
1515
16+ import * as os from 'os' ;
1617import { writeSecureFile , appendSecureFile , mkdirSecure } from './file-permissions' ;
1718import { BrowserManager } from './browser-manager' ;
1819import { handleReadCommand } from './read-commands' ;
@@ -186,7 +187,7 @@ interface SidebarSession {
186187 lastActiveAt : string ;
187188}
188189
189- const SESSIONS_DIR = path . join ( process . env . HOME || '/tmp' , '.gstack' , 'sidebar-sessions' ) ;
190+ const SESSIONS_DIR = path . join ( os . homedir ( ) , '.gstack' , 'sidebar-sessions' ) ;
190191const AGENT_TIMEOUT_MS = 300_000 ; // 5 minutes — multi-page tasks need time
191192const MAX_QUEUE = 5 ;
192193
@@ -235,7 +236,7 @@ function findBrowseBin(): string {
235236 const candidates = [
236237 path . resolve ( __dirname , '..' , 'dist' , 'browse' ) ,
237238 path . resolve ( __dirname , '..' , '..' , '.claude' , 'skills' , 'gstack' , 'browse' , 'dist' , 'browse' ) ,
238- path . join ( process . env . HOME || '' , '.claude' , 'skills' , 'gstack' , 'browse' , 'dist' , 'browse' ) ,
239+ path . join ( os . homedir ( ) , '.claude' , 'skills' , 'gstack' , 'browse' , 'dist' , 'browse' ) ,
239240 ] ;
240241 for ( const c of candidates ) {
241242 try { if ( fs . existsSync ( c ) ) return c ; } catch ( err : any ) {
@@ -248,7 +249,7 @@ function findBrowseBin(): string {
248249const BROWSE_BIN = findBrowseBin ( ) ;
249250
250251function findClaudeBin ( ) : string | null {
251- const home = process . env . HOME || '' ;
252+ const home = os . homedir ( ) ;
252253 const candidates = [
253254 // Conductor app bundled binary (not a symlink — works reliably)
254255 path . join ( home , 'Library' , 'Application Support' , 'com.conductor.app' , 'bin' , 'claude' ) ,
@@ -382,7 +383,7 @@ function createWorktree(sessionId: string): string | null {
382383 if ( gitCheck . exitCode !== 0 ) return null ;
383384 const repoRoot = gitCheck . stdout . toString ( ) . trim ( ) ;
384385
385- const worktreeDir = path . join ( process . env . HOME || '/tmp' , '.gstack' , 'worktrees' , sessionId . slice ( 0 , 8 ) ) ;
386+ const worktreeDir = path . join ( os . homedir ( ) , '.gstack' , 'worktrees' , sessionId . slice ( 0 , 8 ) ) ;
386387
387388 // Clean up if dir exists from prior crash
388389 if ( fs . existsSync ( worktreeDir ) ) {
@@ -633,7 +634,7 @@ function spawnClaude(userMessage: string, extensionUrl?: string | null, forTabId
633634 // fails with ENOENT on everything, including /bin/bash). Instead,
634635 // write the command to a queue file that the sidebar-agent process
635636 // (running as non-compiled bun) picks up and spawns claude.
636- const agentQueue = process . env . SIDEBAR_QUEUE_PATH || path . join ( process . env . HOME || '/tmp' , '.gstack' , 'sidebar-agent-queue.jsonl' ) ;
637+ const agentQueue = process . env . SIDEBAR_QUEUE_PATH || path . join ( os . homedir ( ) , '.gstack' , 'sidebar-agent-queue.jsonl' ) ;
637638 const gstackDir = path . dirname ( agentQueue ) ;
638639 const entry = JSON . stringify ( {
639640 ts : new Date ( ) . toISOString ( ) ,
@@ -674,7 +675,7 @@ function killAgent(targetTabId?: number | null): void {
674675 // Using per-tab files prevents race conditions where one agent's cancel
675676 // signal is consumed by a different tab's agent in concurrent mode.
676677 // When targetTabId is provided, only that tab's agent is cancelled.
677- const cancelDir = path . join ( process . env . HOME || '/tmp' , '.gstack' ) ;
678+ const cancelDir = path . join ( os . homedir ( ) , '.gstack' ) ;
678679 const tabId = targetTabId ?? agentTabId ?? 0 ;
679680 const cancelFile = path . join ( cancelDir , `sidebar-agent-cancel-${ tabId } ` ) ;
680681 try {
@@ -1302,7 +1303,7 @@ async function shutdown(exitCode: number = 0) {
13021303 await browserManager . close ( ) ;
13031304
13041305 // Clean up Chromium profile locks (prevent SingletonLock on next launch)
1305- const profileDir = path . join ( process . env . HOME || '/tmp' , '.gstack' , 'chromium-profile' ) ;
1306+ const profileDir = path . join ( os . homedir ( ) , '.gstack' , 'chromium-profile' ) ;
13061307 for ( const lockFile of [ 'SingletonLock' , 'SingletonSocket' , 'SingletonCookie' ] ) {
13071308 safeUnlinkQuiet ( path . join ( profileDir , lockFile ) ) ;
13081309 }
@@ -1365,7 +1366,7 @@ function emergencyCleanup() {
13651366 console . error ( '[browse] Emergency: failed to save session:' , err . message ) ;
13661367 }
13671368 // Clean Chromium profile locks
1368- const profileDir = path . join ( process . env . HOME || '/tmp' , '.gstack' , 'chromium-profile' ) ;
1369+ const profileDir = path . join ( os . homedir ( ) , '.gstack' , 'chromium-profile' ) ;
13691370 for ( const lockFile of [ 'SingletonLock' , 'SingletonSocket' , 'SingletonCookie' ] ) {
13701371 safeUnlinkQuiet ( path . join ( profileDir , lockFile ) ) ;
13711372 }
@@ -1421,7 +1422,7 @@ async function start() {
14211422 const welcomePath = ( ( ) => {
14221423 // Check project-local designs first, then global
14231424 const slug = process . env . GSTACK_SLUG || 'unknown' ;
1424- const homeDir = process . env . HOME || process . env . USERPROFILE || '/tmp' ;
1425+ const homeDir = os . homedir ( ) ;
14251426 const projectWelcome = `${ homeDir } /.gstack/projects/${ slug } /designs/welcome-page-20260331/finalized.html` ;
14261427 if ( fs . existsSync ( projectWelcome ) ) return projectWelcome ;
14271428 // Fallback: built-in welcome page from gstack install
@@ -1679,7 +1680,7 @@ async function start() {
16791680 // Read ngrok authtoken: env var > ~/.gstack/ngrok.env > ngrok native config
16801681 let authtoken = process . env . NGROK_AUTHTOKEN ;
16811682 if ( ! authtoken ) {
1682- const ngrokEnvPath = path . join ( process . env . HOME || '' , '.gstack' , 'ngrok.env' ) ;
1683+ const ngrokEnvPath = path . join ( os . homedir ( ) , '.gstack' , 'ngrok.env' ) ;
16831684 if ( fs . existsSync ( ngrokEnvPath ) ) {
16841685 const envContent = fs . readFileSync ( ngrokEnvPath , 'utf-8' ) ;
16851686 const match = envContent . match ( / ^ N G R O K _ A U T H T O K E N = ( .+ ) $ / m) ;
@@ -1689,9 +1690,9 @@ async function start() {
16891690 if ( ! authtoken ) {
16901691 // Check ngrok's native config files
16911692 const ngrokConfigs = [
1692- path . join ( process . env . HOME || '' , 'Library' , 'Application Support' , 'ngrok' , 'ngrok.yml' ) ,
1693- path . join ( process . env . HOME || '' , '.config' , 'ngrok' , 'ngrok.yml' ) ,
1694- path . join ( process . env . HOME || '' , '.ngrok2' , 'ngrok.yml' ) ,
1693+ path . join ( os . homedir ( ) , 'Library' , 'Application Support' , 'ngrok' , 'ngrok.yml' ) ,
1694+ path . join ( os . homedir ( ) , '.config' , 'ngrok' , 'ngrok.yml' ) ,
1695+ path . join ( os . homedir ( ) , '.ngrok2' , 'ngrok.yml' ) ,
16951696 ] ;
16961697 for ( const conf of ngrokConfigs ) {
16971698 try {
@@ -2503,7 +2504,7 @@ async function start() {
25032504 // Read ngrok authtoken from env or config file
25042505 let authtoken = process . env . NGROK_AUTHTOKEN ;
25052506 if ( ! authtoken ) {
2506- const ngrokEnvPath = path . join ( process . env . HOME || '' , '.gstack' , 'ngrok.env' ) ;
2507+ const ngrokEnvPath = path . join ( os . homedir ( ) , '.gstack' , 'ngrok.env' ) ;
25072508 if ( fs . existsSync ( ngrokEnvPath ) ) {
25082509 const envContent = fs . readFileSync ( ngrokEnvPath , 'utf-8' ) ;
25092510 const match = envContent . match ( / ^ N G R O K _ A U T H T O K E N = ( .+ ) $ / m) ;
0 commit comments