Skip to content

fix(t3x): auto-build defects found running the LaunchAgent for real - #3

Merged
radroid merged 1 commit into
mainfrom
t3x/auto-build-tcc-fixes
Jul 24, 2026
Merged

fix(t3x): auto-build defects found running the LaunchAgent for real#3
radroid merged 1 commit into
mainfrom
t3x/auto-build-tcc-fixes

Conversation

@radroid

@radroid radroid commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Setting the auto-build up end-to-end on a real machine surfaced three defects. All three
were invisible to the test suite because they only manifest under launchd, or only when
no build exists yet.

1. --print-launchd emitted a plist that can never run

macOS TCC gates ~/Documents, ~/Desktop, ~/Downloads and iCloud Drive. LaunchAgents
get no TCC grant and — unlike apps — never trigger a consent prompt; macOS just returns
EPERM.

Verified with a probe agent that differed only by path:

Path launchd Terminal
~/Documents/t3code/… EPERM OK
identical file copied to /tmp OK OK
listing ~/Documents/t3code EPERM OK

The failure mode is the bad kind: launchctl bootstrap succeeds, launchctl print reports
the job as loaded, and the only evidence is last exit code = 126. It looks installed and
silently never builds.

Now refuses to emit (exit 2) with the three remedies, or --force past it.

2. --install --dry-run named the wrong app

It hardcoded "${T3X_AUTOBUILD_APP_NAME:-T3 Code}.app" and never looked at the dmg — so it
always reported T3 Code.app while a real install replaced T3 Code (Alpha).app. Wrong
about the single fact the preview exists to establish. It also returned before printing
anything when no dmg had been built, i.e. for every first-time user.

Now mounts the dmg read-only for ground truth, falls back to a mirror of upstream's
resolveDesktopProductName(), and warns when the target app is absent — the footgun
where --install silently creates a third app and leaves the one you actually run alone.

3. log() wrote every line twice under launchd

tee -a "$LOG_FILE" >&2 echoes to stderr, and the plist points both StandardOutPath
and StandardErrorPath at that same file. One watcher's output reads exactly like two
racing — which is how this was noticed.

The plist now declares T3X_AUTOBUILD_STDERR_IS_LOG=1. Detecting it via
stat -f '%d:%i' /dev/fd/2 is deliberately not used and is documented in-line: on
macOS that stats the devfs node (2540177495:339), never the redirect target, so it can
never match. I tried it first; it silently didn't work.

Verification

  • TCC guard: real git repo under ~/Documents → exit 2, 0 bytes on stdout, remedies on stderr
  • No false positive from ~/Developer → exit 0, valid 1302-byte plist (plutil -lint OK)
  • --force still emits
  • dry-run with dmg → read 'T3 Code (Alpha).app' from …; without dmg → predicted from apps/desktop/package.json; env override still wins
  • absent-target warning fires; /Applications verified untouched by all dry runs
  • launchd-style dual redirect: 1 occurrence per line (was 2); terminal mode unchanged (3 stderr lines, 3 file lines)
  • bash -n clean

🤖 Generated with Claude Code

Three defects surfaced by actually setting up the LaunchAgent end to end.

1. --print-launchd emitted a plist that could never run. macOS TCC gates
   ~/Documents, ~/Desktop, ~/Downloads and iCloud Drive. launchd jobs get no TCC
   grant and never trigger a consent prompt, so a repo in one of those folders
   yields EPERM: launchd cannot chdir to WorkingDirectory and /bin/bash cannot
   read the script. The failure is near-invisible — bootstrap succeeds,
   'launchctl print' shows the job loaded, and the only evidence is
   'last exit code = 126'. Now detected up front: refuse to emit (exit 2) with
   the three remedies, or --force past it.

2. --install --dry-run named the wrong app. It hardcoded
   "${T3X_AUTOBUILD_APP_NAME:-T3 Code}.app" and never looked at the dmg, so it
   always claimed 'T3 Code.app' while a real install replaced
   'T3 Code (Alpha).app' — wrong about the one fact the preview exists to
   establish. It also bailed before printing anything when no dmg existed yet,
   i.e. for every first-time user. Now mounts the dmg read-only for ground
   truth, falls back to a mirror of resolveDesktopProductName(), and warns when
   the target app is absent (the 'creates a silent third app' footgun).

3. log() wrote every line twice under launchd. It pipes through
   'tee -a "$LOG_FILE" >&2' while the plist points BOTH StandardOutPath and
   StandardErrorPath at that same file, so one watcher's output reads exactly
   like two racing. The plist now declares T3X_AUTOBUILD_STDERR_IS_LOG=1.
   Detection via 'stat -f %d:%i /dev/fd/2' is deliberately NOT used: on macOS
   that stats the devfs node, never the redirect target, so it can never match —
   noted in-line so it doesn't get re-attempted.

Terminal behaviour is unchanged throughout; only the launchd path differs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3855740a-9c3e-4cf0-beb8-5829f19028db

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3x/auto-build-tcc-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@radroid
radroid merged commit b3db05c into main Jul 24, 2026
1 check passed
@radroid
radroid deleted the t3x/auto-build-tcc-fixes branch July 24, 2026 18:00
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