Studio Notebook

Claude Code Atlas

Setup Function And Project Root Preparation

See how setup() establishes the project root, working directory, hooks, and early jobs.

Why this matters

setup() is the moment the process becomes tied to the current workspace. It pins down the cwd, snapshots the hook config before anything can mutate it, and then starts the background jobs the first render depends on.

Lock the cwd and capture hooks first

The order matters. setCwd(cwd) must happen before any code reads the current directory, and the hooks snapshot has to happen before later startup work can silently change what hooks are visible.

export async function setup(
  cwd: string,
  permissionMode: PermissionModeArg,
  allowDangerouslySkipPermissions: boolean,
  worktreeEnabled: boolean,
  worktreeName?: string,
  sessionId?: string,
  worktreePRNumber?: number,
  messagingSocketPath?: string,
) {
  // IMPORTANT: setCwd() must be called before any other code that depends on the cwd
  setCwd(cwd)

  // Capture hooks configuration snapshot to avoid hidden hook modifications.
  // IMPORTANT: Must be called AFTER setCwd() so hooks are loaded from the correct directory
  const hooksStart = Date.now()
  captureHooksConfigSnapshot()
  logForDiagnosticsNoPII('info', 'setup_hooks_captured', {
    duration_ms: Date.now() - hooksStart,
  })

  // Initialize FileChanged hook watcher — sync, reads hook config snapshot
  initializeFileChangedWatcher(cwd)

Keep the project root stable

When setup creates or enters a worktree, it eventually makes the worktree path the session’s stable project root. That root is what later code uses for history, skills, sessions, and command loading.

    process.chdir(worktreeSession.worktreePath)
    setCwd(worktreeSession.worktreePath)
    setOriginalCwd(getCwd())
    // --worktree means the worktree IS the session's project, so skills/hooks/
    // cron/etc. should resolve here. (EnterWorktreeTool mid-session does NOT
    // touch projectRoot — that's a throwaway worktree, project stays stable.)
    setProjectRoot(getCwd())
    saveWorktreeState(worktreeSession)
    // Clear memory files cache since originalCwd has changed
    clearMemoryFileCaches()
    // Settings cache was populated in init() (via applySafeConfigEnvironmentVariables)
    // and again at captureHooksConfigSnapshot() above, both from the original dir's
    // .claude/settings.json. Re-read from the worktree and re-capture hooks.
    updateHooksConfigSnapshot()

setProjectRoot(getCwd()) makes the new worktree path the session’s project identity. cwd may still move during startup, but projectRoot is the stable anchor the rest of the app uses for project-scoped behavior such as command loading, session lookup, and history.

Start the background jobs before first render

Once the workspace is concrete, setup turns on the small background pieces the first turn needs: session memory, command loading, sinks, and the startup analytics beacon.

  if (!isBareMode()) {
    initSessionMemory() // Synchronous - registers hook, gate check happens lazily
    if (feature('CONTEXT_COLLAPSE')) {
      /* eslint-disable @typescript-eslint/no-require-imports */
      ;(
        require('./services/contextCollapse/index.js') as typeof import('./services/contextCollapse/index.js')
      ).initContextCollapse()
      /* eslint-enable @typescript-eslint/no-require-imports */
    }
  }
  void lockCurrentVersion() // Lock current version to prevent deletion by other processes
  logForDiagnosticsNoPII('info', 'setup_background_jobs_launched')

  profileCheckpoint('setup_before_prefetch')
  logForDiagnosticsNoPII('info', 'setup_prefetch_starting')
  const skipPluginPrefetch =
    (getIsNonInteractiveSession() &&
      isEnvTruthy(process.env.CLAUDE_CODE_SYNC_PLUGIN_INSTALL)) ||
    isBareMode()
  if (!skipPluginPrefetch) {
    void getCommands(getProjectRoot())
  }
  initSinks() // Attach error log + analytics sinks and drain queued events

  // Session-success-rate denominator. Emit immediately after the analytics
  // sink is attached — before any parsing, fetching, or I/O that could throw.
  logEvent('tengu_started', {})

initSessionMemory() gets the session memory hook ready. void getCommands(getProjectRoot()) starts the command prefetch using the stable project root. main.tsx can begin other prefetch work earlier, but this is the setup-owned version that depends on the workspace now being concrete. initSinks() must happen before logEvent('tengu_started', {}) so the event does not get dropped.

Takeaways

  • setup() binds the process to the current workspace after global bootstrap finishes.
  • The project root is stable, while cwd can move during startup worktree handling.
  • Background jobs and analytics sinks are launched before the first real render.