Skip to content

Redirect zsh HISTFILE out of the signed app bundle#266

Open
nedtwigg wants to merge 2 commits into
mainfrom
fix/zsh-history-breaks-bundle-signature
Open

Redirect zsh HISTFILE out of the signed app bundle#266
nedtwigg wants to merge 2 commits into
mainfrom
fix/zsh-history-breaks-bundle-signature

Conversation

@nedtwigg

Copy link
Copy Markdown
Member

The bug

Auto-updating to 1.0.1 and then launching produced macOS's "Dormouse Terminal.app is damaged and can't be opened" dialog. The build and updater were fine — the app bricks itself after its first zsh session:

  1. Dormouse spawns zsh with ZDOTDIR pointed at the shell-integration directory inside the signed app bundle.
  2. macOS /etc/zshrc runs before our .zshrc hands ZDOTDIR back to the user, and sets HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history — capturing the in-bundle path (unless the user's own config overrides HISTFILE, which default-ish setups don't).
  3. On shell exit, zsh writes .zsh_history into the sealed bundle. Any file added inside a signed bundle invalidates the signature; the next launch fails Gatekeeper.

codesign --verify on the affected install showed exactly this:

/Applications/Dormouse Terminal.app: a sealed resource is missing or invalid
file added: .../Contents/Resources/_up_/sidecar/shell-integration/zsh/.zsh_history

v1.0.0 shipped the same integration, so every macOS zsh user is exposed; it manifests on the first launch after a zsh session has exited. A bricked install can't launch its own updater — recovery is deleting the stray file or re-downloading the DMG — so this wants a hotfix release.

The fix

In the bundled .zshrc, after sourcing the user's rc, redirect a HISTFILE that points into our directory back to USER_ZDOTDIR. Running after the user's rc also catches user .zshenv/.zprofile files that set HISTFILE relative to ZDOTDIR while it was still pinned to ours, and guarantees a HISTFILE the user sets themselves is never touched. Also corrects the header comment's wrong claim that the shipped directory is read-only, and documents the invariant in terminal-escapes.md.

Non-issue checked along the way: Apple Terminal's session-restore (/etc/zshrc_Apple_Terminal) writes .zsh_sessions/ + .zsh_history into ZDOTDIR via a later hook, but it only loads when TERM_PROGRAM=Apple_Terminal — Dormouse unconditionally sets TERM_PROGRAM=iTerm.app (pty-core.js), so that path can't fire in a Dormouse-spawned shell.

Verification

Spawned zsh exactly as Dormouse does (ZDOTDIR → integration dir, USER_ZDOTDIR set, TERM_PROGRAM=iTerm.app):

  • default config → HISTFILE lands in USER_ZDOTDIR/.zsh_history
  • user rc setting its own HISTFILE → untouched
  • interactive session running a command then exiting → integration dir still contains only its 4 tracked dotfiles; the history line landed in the user dir

pnpm lint:specs OK; sidecar pty-core.test.js 70/70 pass.

🤖 Generated with Claude Code

macOS /etc/zshrc runs while ZDOTDIR still points at the bundled
shell-integration directory and sets HISTFILE inside it. On shell exit
zsh then writes .zsh_history into the signed app bundle, breaking its
code signature — the next launch fails Gatekeeper with "Dormouse
Terminal.app is damaged and can't be opened".

Our .zshrc now redirects a HISTFILE that points into our directory back
to the user's ZDOTDIR, after sourcing the user's rc so a HISTFILE they
set themselves is never touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 16, 2026

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9f0fab3
Status: ✅  Deploy successful!
Preview URL: https://f9a7c1de.mouseterm.pages.dev
Branch Preview URL: https://fix-zsh-history-breaks-bundl.mouseterm.pages.dev

View logs

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The fix is sound and I traced it end to end: DORMOUSE_ZDOTDIR is reliably set in .zshenv before this check runs, the redirect target reproduces exactly what /etc/zshrc would have chosen without Dormouse, running after the user's rc correctly leaves a user-set HISTFILE untouched, and the ${...}/* pattern does match a dotfile like .zsh_history in a [[ ]] conditional (the leading-. rule is filename-generation only, not pattern matching). Sidecar tests 70/70, lint:specs clean, spec prose matches the code.

One robustness edge case worth hardening — see the inline suggestion. Not a blocker; the default /Applications/Dormouse Terminal.app/... path is unaffected.

Comment thread standalone/sidecar/shell-integration/zsh/.zshrc Outdated
Default zsh treats unquoted parameter expansions in [[ == ]] patterns
literally, but a user rc that enables GLOB_SUBST (sh/ksh emulation)
turns the install path into a pattern — glob metacharacters in the path
would then silently defeat the guard. Quoting keeps the directory
literal while /* stays a wildcard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants