Build1 publisher3 min readPublished
Execing the binary inside Google Chrome.app registers com.google.Chrome with LaunchServices. That registration outlives the script. The durable repair is a second bundle id, not a kill loop.
The Engineer · Build desk
Follow any of these and your For You feed starts watching them — no settings page required.
Compiled by The EngineerSomething wrong?How this is made
Playwright's `channel: 'chrome'` does not ask macOS to open an application. It execs the binary inside /Applications/Google Chrome.app, and LaunchServices reads that as com.google.Chrome having launched [3]. The belief outlives the automation process [3], which is how `open -a "Google Chrome"` returns -600 (procNotFound) with no Chrome window anywhere on screen [2].
That framing decides the repair. A kill clears the symptom and the next Playwright run reinstates it, because the automation browser and the human's browser answer to the same bundle id [4]. So the author takes the id apart: copy the bundle to /Applications/Chrome Automation.app, set CFBundleIdentifier to com.google.ChromeAutomation and CFBundleName to Chrome Automation with PlistBuddy [5], then confirm with `lsappinfo list` that LaunchServices now sees two distinct apps [6].
Editing Info.plist carries a cost that is easy to hit and unpleasant to diagnose. Once the plist changes, Google's signature is invalid, and Library Validation SIGKILLs Chrome's helper processes on the spot, exit 137 [7]. The `codesign --force --deep --sign -` line restores internal consistency across the bundle, helpers included; it is an ad-hoc signature rather than Apple's, which the author accepts for local personal use [8]. The rebuild itself only fires when the copy is missing or its version differs from the source app [15], and it is skipped when `pgrep` finds the automation Chrome live, since copying over a running bundle replaces its own binary underneath it [14].
Then the part with no clean version. `channel:'chrome'` resolves through a path hardcoded in playwright-core/lib/coreBundle.js, and no environment variable overrides it [9], so the script seds that string inside node_modules to point at the automation binary [10]. Any `npm install` or `playwright install` wipes the edit, which is why the author treats node_modules as volatile and reapplies on a timer [11]. The launchd agent uses StartInterval 21600 with RunAtLoad [12], and the script finishes in 1 second [13]. Four runs a day, about four seconds of work [1]. A reinstall at the wrong moment leaves `channel:'chrome'` pointed back at the user's Chrome for as long as six hours, unless a login fires RunAtLoad sooner [2]. That gap is the number that matters.
This is your problem only if the host is macOS, the script uses `channel:'chrome'` rather than Playwright's bundled Chromium, and a person clicks the Dock icon on the same machine. It is one machine's account, dated 2026-09-01 [1]. The write-up does not report trying the obvious alternative of installing a separate Chrome build and pointing Playwright at it directly, and neither Playwright nor Apple is on record here describing the LaunchServices behaviour. What the evidence does support is the invariant the author names: shared bundle id, shared interference [4].
Ranked by verification strength, evidence, and original report placement.
Re-signing the whole bundle ad-hoc with `codesign --force --deep --sign -`, including Chrome's helper binaries, restores internal consistency; it is not Apple's official signature but launches fine for local personal use.
On 2026-09-01, during a window when only automation scripts were running on the author's Mac, opening "Google Chrome" from the Dock started failing.
The error from `open -a "Google Chrome"` was error -600 (procNotFound); no GUI Chrome window was open, yet macOS was convinced Chrome was already running, and clicking the Dock icon failed.
With channel:'chrome', Playwright directly execs the binary inside /Applications/Google Chrome.app; LaunchServices interprets this as com.google.Chrome having launched, and that belief lingers after the automation process exits, so launches from the Dock or Spotlight no longer go through.
The repair script ~/.claude/scripts/chrome-automation-repair.sh copies /Applications/Google Chrome.app to /Applications/Chrome Automation.app and uses PlistBuddy to set CFBundleIdentifier to com.google.ChromeAutomation and CFBundleName to Chrome Automation.
Rewriting CFBundleIdentifier alone is enough for LaunchServices to register the copy as an app distinct from regular Chrome, confirmable with `lsappinfo list`; from then on the automation side never interferes with com.google.Chrome on the Dock side.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One console, fully shown
Every fact in this story traces to the same dev.to build log: the -600 error, the LaunchServices theory, the script, the plist. What lifts it above anecdote is that the author shows the commands rather than describing them, and names two things a reader can verify alone — `lsappinfo list` for the separate registration and the hardcoded path in playwright-core/lib/coreBundle.js. What holds it down is that no one else has weighed in: there's no upstream issue, no Apple or Google documentation cited for the Library Validation behaviour, and the author has deliberately withheld the launch agent's filename.
One Mac, four runs a day
The only installation on record is the author's own workstation, where a launch agent has been rebuilding Chrome Automation.app and re-patching node_modules since early September. No other developer, team, or project is reported using the bundle-id split, and the only usage volume in evidence — four scheduled repairs a day — describes that same machine.
Permanent, with a timer attached
The bundle-id split probably does earn the word permanent for LaunchServices, since a distinct identifier is a durable fact about the copied app. The rest of the fix is not permanent at all: the path rewrite lives inside node_modules, any reinstall removes it, and only a six-hourly agent puts it back. dev.to concedes as much, and also concedes that ad-hoc signing costs the Keychain's Always Allow on every Chrome update. The framing runs a little ahead of a mechanism the author himself describes as volatile by nature.
Nothing to sell, a series to continue
The author has no product in this and no vendor relationship to protect, which is likely why the unflattering details survive: the rebuild that gets skipped, the patch that gets wiped, and a Keychain regression that turned up a day later. The pull that does exist is platform-shaped: a numbered-pitfalls headline in an ongoing series on a developer publishing site rewards a tidy narrative of accident and permanent fix over an inconclusive report.
Falsifiable but unfalsified
We hold this in the middle rather than low because the diagnosis is precise enough for a reader to confirm or demolish in one command on any Mac with Playwright installed. It remains unverified for now, though nothing about it resists checking. The mechanism claims about LaunchServices and the coreBundle.js path are the load of the story, and both stand on a single first-person account written days after the incident.
build
A port-9223 liveness probe wiped one job's Instagram login every night at 23:001 publisher
build
The 46GB Leak Your RSS Alert Cannot See: macOS Compressed Memory Breaks Threshold Monitoring1 publisher
build
Every setting was correct and the job died anyway: the assertion nobody writes1 publisher
build
Exit 0 is not a health check: three weeks of macOS backups that copied nothing1 publisher
Publishers with included, body-backed reporting in this cluster.
1 article · September 7, 2026