Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions apps/desktop/src/main/lib/agent-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ exec "${realCodex}" -c 'notify=["bash","${notifyPath}"]' "$@"
function createZshWrapper(): void {
// Create .zprofile to source user's .zprofile (runs for login shells before .zshrc)
// This is critical - without it, brew/nvm PATH setup in ~/.zprofile is skipped
// Don't change ZDOTDIR here - we need our .zshrc to run after this
const zprofilePath = path.join(ZSH_DIR, ".zprofile");
const zprofileScript = `# Superset zsh profile wrapper
_superset_home="\${SUPERSET_ORIG_ZDOTDIR:-$HOME}"
Expand All @@ -123,11 +122,31 @@ _superset_home="\${SUPERSET_ORIG_ZDOTDIR:-$HOME}"

// Create .zshrc to source user's .zshrc then prepend our bin
const zshrcPath = path.join(ZSH_DIR, ".zshrc");
const zshrcScript = `# Superset zsh rc wrapper
_superset_home="\${SUPERSET_ORIG_ZDOTDIR:-$HOME}"
[[ -f "$_superset_home/.zshrc" ]] && source "$_superset_home/.zshrc"
const zshrcScript = `# Superset zsh initialization wrapper
# This file intercepts zsh startup to ensure ~/.superset/bin is in PATH

# Restore original ZDOTDIR for any nested shells
export ZDOTDIR="\${SUPERSET_ORIG_ZDOTDIR:-$HOME}"

ORIG_ZDOTDIR="$ZDOTDIR"

# Source the same user-level startup files zsh normally would
if [[ -f "$ORIG_ZDOTDIR/.zshenv" ]]; then
source "$ORIG_ZDOTDIR/.zshenv"
fi

# Source user's real zshrc if it exists
if [[ -f "$ORIG_ZDOTDIR/.zshrc" ]]; then
source "$ORIG_ZDOTDIR/.zshrc"
fi

# Finish login shell sequence
if [[ -o login && -f "$ORIG_ZDOTDIR/.zlogin" ]]; then
source "$ORIG_ZDOTDIR/.zlogin"
fi

# Prepend superset bin to PATH (after user's rc has run)
export PATH="$HOME/${SUPERSET_DIR_NAME}/bin:$PATH"
export ZDOTDIR="$_superset_home"
`;
fs.writeFileSync(zshrcPath, zshrcScript, { mode: 0o644 });
console.log("[agent-setup] Created zsh wrapper");
Comment on lines +125 to 152

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the zsh startup sequence violation.

The current implementation sources .zshenv and .zlogin from within .zshrc, which violates zsh's expected startup order and can cause initialization issues.

Expected zsh startup sequence: .zshenv → .zprofile → .zshrc → .zlogin

Current sequence with wrappers:

  1. zsh sources our .zprofile → sources user's .zprofile
  2. zsh sources our .zshrc → sources user's .zshenv ❌ (too late), .zshrc ✓, .zlogin ❌ (too early)

Impact:

  • Environment variables in user's .zshenv are unavailable when their .zprofile runs (breaks PATH setup dependencies)
  • Commands in user's .zlogin run before .zshrc completes (breaks timing assumptions)

Recommended fix:

Create separate wrapper files for each startup file to maintain the correct sequence:

 function createZshWrapper(): void {
+	// Create .zshenv wrapper (runs first, always)
+	const zshenvPath = path.join(ZSH_DIR, ".zshenv");
+	const zshenvScript = `# Superset zsh environment wrapper
+# Restore original ZDOTDIR for nested shells
+export ZDOTDIR="\${SUPERSET_ORIG_ZDOTDIR:-$HOME}"
+
+# Source user's .zshenv if it exists
+[[ -f "$ZDOTDIR/.zshenv" ]] && source "$ZDOTDIR/.zshenv"
+`;
+	fs.writeFileSync(zshenvPath, zshenvScript, { mode: 0o644 });
+
 	// Create .zprofile to source user's .zprofile (runs for login shells before .zshrc)
 	// This is critical - without it, brew/nvm PATH setup in ~/.zprofile is skipped
 	const zprofilePath = path.join(ZSH_DIR, ".zprofile");
 	const zprofileScript = `# Superset zsh profile wrapper
-_superset_home="\${SUPERSET_ORIG_ZDOTDIR:-$HOME}"
-[[ -f "$_superset_home/.zprofile" ]] && source "$_superset_home/.zprofile"
+[[ -f "$ZDOTDIR/.zprofile" ]] && source "$ZDOTDIR/.zprofile"
 `;
 	fs.writeFileSync(zprofilePath, zprofileScript, { mode: 0o644 });
 
 	// Create .zshrc to source user's .zshrc then prepend our bin
 	const zshrcPath = path.join(ZSH_DIR, ".zshrc");
 	const zshrcScript = `# Superset zsh initialization wrapper
-# This file intercepts zsh startup to ensure ~/.superset/bin is in PATH
-
-# Restore original ZDOTDIR for any nested shells
-export ZDOTDIR="\${SUPERSET_ORIG_ZDOTDIR:-$HOME}"
-
-ORIG_ZDOTDIR="$ZDOTDIR"
-
-# Source the same user-level startup files zsh normally would
-if [[ -f "$ORIG_ZDOTDIR/.zshenv" ]]; then
-  source "$ORIG_ZDOTDIR/.zshenv"
-fi
-
 # Source user's real zshrc if it exists
-if [[ -f "$ORIG_ZDOTDIR/.zshrc" ]]; then
-  source "$ORIG_ZDOTDIR/.zshrc"
-fi
-
-# Finish login shell sequence
-if [[ -o login && -f "$ORIG_ZDOTDIR/.zlogin" ]]; then
-  source "$ORIG_ZDOTDIR/.zlogin"
-fi
-
-# Prepend superset bin to PATH (after user's rc has run)
-export PATH="$HOME/${SUPERSET_DIR_NAME}/bin:$PATH"
+[[ -f "$ZDOTDIR/.zshrc" ]] && source "$ZDOTDIR/.zshrc"
 `;
 	fs.writeFileSync(zshrcPath, zshrcScript, { mode: 0o644 });
+
+	// Create .zlogin wrapper (runs last for login shells)
+	const zloginPath = path.join(ZSH_DIR, ".zlogin");
+	const zloginScript = `# Superset zsh login wrapper
+# Source user's .zlogin if it exists
+[[ -f "$ZDOTDIR/.zlogin" ]] && source "$ZDOTDIR/.zlogin"
+
+# Prepend superset bin to PATH (runs at the very end)
+export PATH="$HOME/${SUPERSET_DIR_NAME}/bin:$PATH"
+`;
+	fs.writeFileSync(zloginPath, zloginScript, { mode: 0o644 });
+
 	console.log("[agent-setup] Created zsh wrapper");
 }

Note: By setting ZDOTDIR once in .zshenv and using $ZDOTDIR in subsequent wrappers, nested shells automatically use the original ZDOTDIR without needing to re-export it in every file.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/desktop/src/main/lib/agent-setup.ts around lines 125-152, the zsh
wrapper currently sources the user's .zshenv and .zlogin from inside the .zshrc
wrapper, violating zsh's startup order; instead create separate wrapper files
that preserve the correct sequence: write a .zshenv wrapper that sets
SUPERSET_ORIG_ZDOTDIR (if not already set) and exports ZDOTDIR to point to the
wrapper directory, then write a .zprofile wrapper that sources
"$ZDOTDIR/.zprofile" if present and then runs any superset .zprofile logic,
write a .zshrc wrapper that sources "$ZDOTDIR/.zshrc" (do not source .zshenv or
.zlogin here) and finally write a .zlogin wrapper that sources
"$ZDOTDIR/.zlogin" after .zshrc; ensure each wrapper reads $ZDOTDIR (so nested
shells inherit original location), prepend the superset bin to PATH only in the
appropriate wrapper (e.g., .zprofile or .zshrc as needed), and write each file
with correct permissions and a console log for creation.

Expand Down
Loading