Skip to content

fix(service): Mac PTY wrap parity + remove DISABLE_AUTOUPDATER#16

Merged
crisandrews merged 1 commit intomainfrom
fix/mac-pty-parity-and-remove-autoupdater
Apr 17, 2026
Merged

fix(service): Mac PTY wrap parity + remove DISABLE_AUTOUPDATER#16
crisandrews merged 1 commit intomainfrom
fix/mac-pty-parity-and-remove-autoupdater

Conversation

@crisandrews
Copy link
Copy Markdown
Owner

Follow-up to #9. Two corrections to the service-install defaults:

1. macOS PTY wrap parity.
generatePlist now wraps the invocation in /usr/bin/script -q /dev/null <claudeBin> <args> (BSD syntax). launchd services run without a controlling TTY by default, same as systemd, so the SessionEnd-hook failure mode #9 fixed on Linux could hit Mac too. This is a preemptive parity fix using the same mechanism Linux got.

Validated locally: script -q /dev/null claude --version and --help both return exit 0 with clean output, so the wrapper doesn't break the happy path.

2. Remove Environment=DISABLE_AUTOUPDATER=1 from the systemd unit.
JD called this "defense-in-depth" in #9 — the PTY wrap alone suffices to fix the crash-loop. Setting the env var disables a Claude Code feature it ships intentionally, which runs against this plugin's principle of being an OS-level envelope that doesn't modify Claude Code internals. Auto-update returns to working as designed on both platforms.

Test plan

  • script -q /dev/null /bin/echo hello → exit 0
  • script -q /dev/null claude --version → exit 0
  • script -q /dev/null claude --help → exit 0
  • buildPlan("install", ...) for darwin emits plist with /usr/bin/script -q /dev/null prepended in ProgramArguments
  • buildPlan("install", ...) for linux emits unit without DISABLE_AUTOUPDATER=1 line
  • (Deferred) Live reproduction of the crash loop on macOS — would require installing an actual launchd service and waiting for Claude Code to auto-update mid-run, which is invasive. The wrapper is validated to work on the happy path; preemptive parity with the Linux fix is the pragmatic choice.

…TER env var

Two corrections to the service install defaults introduced in #9.

1. `generatePlist` (macOS/launchd) now wraps the invocation in
   `/usr/bin/script -q /dev/null <claudeBin> <args>` using BSD syntax.
   launchd services run without a controlling TTY by default, so the same
   SessionEnd-hook failure mode #9 fixed on Linux systemd could in
   principle hit Mac too. The wrapper is a preemptive parity fix using the
   same mechanism, validated locally on Mac (script -q works with claude
   --version / --help cleanly).

2. Remove `Environment=DISABLE_AUTOUPDATER=1` from the systemd unit.
   JD's own PR body called this a "defense-in-depth" extra — the PTY
   wrap alone suffices to fix the crash-loop. Setting the env var alters
   Claude Code's natural behavior (disables a feature it ships
   intentionally), which runs against this plugin's principle of being
   an OS-level envelope that doesn't modify Claude Code internals.
   Auto-update returns to working as designed.

Together these two changes give Mac and Linux equivalent protection
against the original crash-loop without forcing a config change on
Claude Code itself.
@crisandrews crisandrews merged commit f73f3aa into main Apr 17, 2026
JD2005L added a commit to JD2005L/ClawCode that referenced this pull request Apr 17, 2026
Follow-up to crisandrews#16 with a clarified rationale for keeping the env var.

The PTY wrap from crisandrews#9 fixes the SessionEnd-hook failure that turns graceful
exit into code 1 and causes restart churn. DISABLE_AUTOUPDATER=1 addresses
a different problem: Claude Code's in-process auto-updater regenerates
files it manages mid-run, including the resume-on-restart wrapper script
generated by crisandrews#7. A long-running daemon rewriting its own ExecStart target
while live is a file-integrity issue, separate from the crash loop, and
the PTY wrap does nothing for it.

On the "don't modify Claude Code internals" principle from crisandrews#16: the
principle stands, but DISABLE_AUTOUPDATER is a documented env var Claude
Code exposes for this use case. Setting it is a supported interface, not
a monkey-patch. The restored comment names the env var and the specific
file-regeneration scenario inline so future readers see the intent.

Also relevant to crisandrews#12 (/agent:update skill): the skill's explicit-manual-
update flow assumes in-process auto-update is off in service mode. With
auto-update running again, the skill competes with an updater that may
silently rewrite service files behind the operator.
crisandrews added a commit that referenced this pull request Apr 17, 2026
Supersedes #17 (conflict resolution against post-#8 main). JD's #17
targeted pre-#8 main so the block collided with the newly-added HOME/TERM
Environment lines. This commit places the same 8-line block immediately
after HOME/TERM so systemd sees all three env vars side by side.

The rationale (verbatim from #17, which is correct):

PTY wrap and DISABLE_AUTOUPDATER fix different problems:
- PTY wrap: SessionEnd hook needs a controlling terminal to spawn
  /bin/sh at graceful shutdown.
- DISABLE_AUTOUPDATER: prevents Claude Code's in-process auto-updater
  from regenerating daemon-relevant files (including the
  resume-on-restart wrapper from #7) while the daemon is running.

#16 incorrectly treated DISABLE_AUTOUPDATER as redundant
defense-in-depth against the crash loop. It is actually addressing the
file-integrity scenario, which the PTY wrap does not cover. Setting
DISABLE_AUTOUPDATER is also using a documented Claude Code env var,
not monkey-patching internals.

macOS plist remains unchanged (JD's #17 scope; parity can follow once
the systemd-side default settles).

Co-Authored-By: JD2005L <34459020+JD2005L@users.noreply.github.com>
crisandrews added a commit that referenced this pull request Apr 17, 2026
Supersedes #17 (conflict resolution against post-#8 main). JD's #17
targeted pre-#8 main so the block collided with the newly-added HOME/TERM
Environment lines. This commit places the same 8-line block immediately
after HOME/TERM so systemd sees all three env vars side by side.

The rationale (verbatim from #17, which is correct):

PTY wrap and DISABLE_AUTOUPDATER fix different problems:
- PTY wrap: SessionEnd hook needs a controlling terminal to spawn
  /bin/sh at graceful shutdown.
- DISABLE_AUTOUPDATER: prevents Claude Code's in-process auto-updater
  from regenerating daemon-relevant files (including the
  resume-on-restart wrapper from #7) while the daemon is running.

#16 incorrectly treated DISABLE_AUTOUPDATER as redundant
defense-in-depth against the crash loop. It is actually addressing the
file-integrity scenario, which the PTY wrap does not cover. Setting
DISABLE_AUTOUPDATER is also using a documented Claude Code env var,
not monkey-patching internals.

macOS plist remains unchanged (JD's #17 scope; parity can follow once
the systemd-side default settles).

Co-authored-by: crisandrews <crisandrews@users.noreply.github.com>
Co-authored-by: JD2005L <34459020+JD2005L@users.noreply.github.com>
crisandrews added a commit that referenced this pull request Apr 17, 2026
Summary of changes in this release (full detail in CHANGELOG.md):

Added
- Resume-on-restart wrapper for service mode (#7)
- Service hardening defaults: HOME/TERM env, StartLimitBurst guard, persistent log path (#8)
- /agent:update skill + heartbeat version-check with day-gate and per-version dedupe (#12)

Fixed
- WORKSPACE resolution so memory_search hits user's project dir, not plugin dir (#6, closes #5)
- Linux systemd crash loop after Claude Code auto-updates mid-run — PTY wrap in ExecStart + DISABLE_AUTOUPDATER=1 for file-integrity (#9, #17/#18)
- macOS launchd PTY wrap parity (#16)
- Cross-user /agent:import discovery + post-import path sanity check (#10)

Performance
- reconcile-crons.sh fast-path on steady-state sessions (#11)

Thanks to @JD2005L for the whole batch.
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.

1 participant