Keep the hook, the login item and the app's deck working across upgrades and opt-outs - #1942
Merged
Merged
Conversation
…and and the login item process.execPath has its links resolved, so a Homebrew or linked tarball node wrote a versioned path into settings.json and the login job, and the next upgrade left every hook exiting 127. Both now use the PATH entry that links to the running node, or Homebrew's opt/ link, and fall back to execPath.
The job carried only the scope directories, so a deck started at login ran without AGENTS_DECK_NO_REPORTS, AGENTS_DECK_NO_LAN and the rest of the documented settings the installing shell had set. Every variable in README's environment table now rides along by name, and nothing else from the shell does.
…shell sets An app opened from the Dock or a launcher read only PATH from the login shell, so its deck ran without AGENTS_DECK_NO_REPORTS, AGENTS_DECK_NO_LAN, CLAUDE_CONFIG_DIR and the rest of README's environment table. The shell is now asked for those names alone, and the app takes them onto its own environment before it looks for a deck.
Only npx's cache was recognised, so the other one-off runners got a login item naming a file in their cache and an in-app npm i -g the away-update repeated every thirty minutes without moving the running copy. They are now refused both, and the banner shows the global install as a command instead.
…romising one With no systemd, systemctl was missing or refused and that read as a unit file systemd would pick up at the next login, so the first start said ccdeck would start at login and never offered again. installService now asks whether systemd is the init before writing anything, and reports the refusal.
…t only Environment= systemd resolves specifiers in StandardOutput=, StandardError= and ExecStart too, and variables in ExecStart, so a deck home or install path with a % in it named somewhere else, or failed the unit outright. Every value is now escaped, and an ExecStart argument with a quote or backslash is quoted with systemd's escapes.
BarganConstantin
force-pushed
the
fix/install-and-env
branch
from
October 5, 2026 00:00
df8aecf to
f507184
Compare
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
process.execPathhas its links resolved, so a Homebrew or linked-tarball node put a versioned path (…/Cellar/node/<version>/bin/node,…/node-v24.21.0-linux-x64/bin/node) intosettings.jsonand the login job. The next upgrade deleted it, so every hook exited 127 and Claude Code showed a hook error on every tool call. Both now use the first PATH entry that links to the running node, or Homebrew'sopt/<formula>/bin/node, and fall back toexecPathwhen neither exists (newsrc/server/stable-node.mjs). The desktop app'sCCDECK_HOOK_RUNTIMEoverride still takes precedence.CLAUDE_CONFIG_DIR/CCDECK_HOME/CODEX_HOME, so a deck started at login ran withoutAGENTS_DECK_NO_REPORTS,AGENTS_DECK_NO_LANand the other settings. Every variable in README's environment table is now carried by name (SETTING_VARS), only when it is set, and nothing else from the shell goes in. A test checks the list against README's table.findDecksandwriteLauncheruse the rightCLAUDE_CONFIG_DIR), and passes them to the deck it starts. If the app was launched with a value already set, that value wins.npm i -gthat the away-update repeated every 30 minutes without changing the running version. Now there is no login item, the upgrade is refused with a newone_offreason (mode null), and the banner showsnpm i -g ccdeck@latestas a command to run.installServicechecks/run/systemd/systembefore writing anything. The first start used to say "will now start when you log in" and save the item as installed. Now it reports that it couldn't set one up.%everywhere.StandardOutput=/StandardError=andExecStartnow escape%as%%. ExecStart also escapes$as$$, and quotes any argument containing a quote or backslash, using systemd's escapes.Verification
npm run typecheck: clean.hook-command-stable-node.test.ts:settings.jsonnamed'/home/…/node-v24.21.0-linux-x64/bin/node'where the PATH link was expected. The login job did the same. A simulated upgrade (repointcurrent, delete the old version) leaves the new path working.login-item-opt-outs.test.ts: the plist and the unit had noAGENTS_DECK_NO_REPORTS. Now checks that every variable in README's table is carried and that unrelated variables are not.desktop-shell-env.test.ts: a real/bin/shstands in for$SHELL, with a profile that exports the opt-outs. Before the fix only PATH reached the deck (checked first through the oldshellPath+startDeckpath). The block is registered inskip-gates.mjs, with the Windows counts in the inventory test andpublish.ymlupdated.one-off-runner.test.ts:upgradeBlockreturnednull(mode "install") for pnpm dlx, older pnpm dlx, bunx and yarn dlx layouts.login-item-no-systemd.test.ts:installServicereturnedok: true, how: "file-only"and wrote the unit whensystemctlwas missing.systemd-unit-escaping.test.ts:StandardOutput=andExecStartkept a bare%. Also checked against systemd 259 offline (systemd-analyze verifyon a temp unit file). The old output gaveFailed to resolve unit specifiers in /tmp/100%Qbackup/deck.log, ignoringand a fatal ExecStart error. The new output verifies clean.app-host.test.ts's three PATH cases now callshellEnv(...).PATH. The skip-gate register counts 27 win32 sites and 56 Windows skips.