Skip to content

fix(install): write claude-cowork alias via temp+mv to survive legacy symlink#140

Merged
johnzfitch merged 1 commit into
masterfrom
claude/fix-launcher-symlink-clobber
Jun 13, 2026
Merged

fix(install): write claude-cowork alias via temp+mv to survive legacy symlink#140
johnzfitch merged 1 commit into
masterfrom
claude/fix-launcher-symlink-clobber

Conversation

@johnzfitch

Copy link
Copy Markdown
Owner

Summary

Fixes the upgrade-time regression diagnosed in #138 (credit to @rmorison), using the symlink-safe atomic-replace variant instead of an unconditional rm.

The bug: Pre-8ad9bd7 installs created ~/.local/bin/claude-cowork as a symlink to claude-desktop. The current create_launcher() writes the alias with cat > ~/.local/bin/claude-cowork, which follows that legacy symlink and overwrites the just-written claude-desktop launcher (~3.4 KB) with the 56-byte wrapper body. claude-desktop then execs itself in an infinite loop — 99% CPU, no Electron child, no logs. The installer reports success because the in-tree COWORK_WRAPPER_OK check only validates claude-cowork. Confirmed on upgrades to current master.

The fix: Write the wrapper to a temp file in the same directory and mv -f it into place. mv replaces the symlink itself rather than writing through it, atomically, with no unconditional rm on a live path — so the legacy symlink is broken cleanly and claude-desktop is never touched.

Why this variant over rm -f + cat

@rmorison's original fix (rm -f before the cat) is correct and bounded. This PR uses the temp-file + mv form instead because it avoids deleting a live path entirely and is atomic (no window where the alias is missing or half-written). Same outcome, slightly more defensive.

Test plan

  • bash -n install.sh
  • Repro the legacy state (claude-cowork symlinked → claude-desktop), run the patched alias-writing block, and confirm: claude-desktop retains the full launcher (not clobbered), claude-cowork is a real file (the wrapper), both are regular files (neither a symlink), and no temp files leak.
claude-desktop is symlink? no   (full launcher content intact)
claude-cowork  is symlink? no   (wrapper content)
leftover temps: 0

https://claude.ai/code/session_01JN4faDKhhBv6qGFvPWRF21


Generated by Claude Code

… symlink

Pre-8ad9bd7 installs left ~/.local/bin/claude-cowork as a symlink to
claude-desktop. The cat-redirect that writes the alias would follow that
symlink and overwrite the freshly-written claude-desktop launcher with
the 56-byte wrapper, which then exec's itself in an infinite loop (99%
CPU, no UI, no logs) on the next launch. Confirmed on upgrades to current
master.

Write the wrapper to a temp file in the same directory and mv it into
place. mv replaces the symlink itself rather than its target, atomically,
and avoids an unconditional rm on a live path.

Co-authored-by: rmorison <220009+rmorison@users.noreply.github.com>

https://claude.ai/code/session_01JN4faDKhhBv6qGFvPWRF21
Copilot AI review requested due to automatic review settings June 13, 2026 23:53
@johnzfitch johnzfitch merged commit 1584a67 into master Jun 13, 2026
4 checks passed

Copilot AI left a comment

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.

Pull request overview

This PR fixes an upgrade-time installer regression where a legacy ~/.local/bin/claude-cowork -> claude-desktop symlink could cause the installer to overwrite the claude-desktop launcher when writing the claude-cowork wrapper. The change updates alias creation to use an atomic temp-file write + mv -f to replace the symlink itself safely.

Changes:

  • Write the claude-cowork wrapper to a temp file in ~/.local/bin and atomically replace the destination via mv -f to avoid following legacy symlinks.
  • Add explanatory comments documenting the legacy-symlink failure mode and why the temp+mv approach is used.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread install.sh
# Pre-8ad9bd7 installs left ~/.local/bin/claude-cowork as a symlink to
# claude-desktop. A plain `cat > claude-cowork` would FOLLOW that symlink
# and overwrite the just-written claude-desktop launcher with this wrapper,
# which then exec's itself in an infinite loop (PR #138). Write to a temp
Comment thread install.sh
# which then exec's itself in an infinite loop (PR #138). Write to a temp
# file in the same dir and `mv` it into place: mv replaces the symlink
# itself rather than its target, atomically, with no unconditional rm.
local cowork_alias="$HOME/.local/bin/claude-cowork"
Comment thread install.sh
Comment on lines +813 to 817
cowork_tmp="$(mktemp "$HOME/.local/bin/.claude-cowork.XXXXXX")" || die "Failed to create temp file for claude-cowork wrapper"
cat > "$cowork_tmp" << 'WRAP'
#!/bin/bash
exec "$HOME/.local/bin/claude-desktop" "$@"
WRAP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants