Skip to content

fix(dev): spawn pinned local tailwind bin directly, not bunx @latest#493

Merged
markhayden merged 2 commits into
mainfrom
fix/tailwind-direct-spawn
Jun 12, 2026
Merged

fix(dev): spawn pinned local tailwind bin directly, not bunx @latest#493
markhayden merged 2 commits into
mainfrom
fix/tailwind-direct-spawn

Conversation

@markhayden

Copy link
Copy Markdown
Owner

Summary

Fixes the dev-loop tailwind leak surfaced during #459 verification (documented as a known gap in dev-loop.md by #491) — straight to PR, no issue, per kickoff decision.

Two defects, one root cause:

  • Leak: nodeSpawn('bunx', ['@tailwindcss/cli', …, '--watch=always']) produced a volta-shim → bunx → node grandchild chain. tailwindChild.kill('SIGTERM') hit only the wrapper; the node --watch process reparented to launchd and watched files forever (observed twice, pids 91436/91590).
  • Version drift: bunx downloaded floating @tailwindcss/cli@latest at every dev boot, ignoring the pinned ^4.2.4 devDependency.

Changes

  • scripts/dev.ts: spawn the lockfile-pinned node_modules/.bin/tailwindcss directly under bun — the child pid IS the tailwind process, so SIGTERM reaches it. No wrapper resolution at boot.
  • package.json build:css: bunx @tailwindcss/cli → bare tailwindcss (node_modules/.bin is on package-script PATH). CI workflows run bun install --frozen-lockfile first, so both pipelines resolve the pinned version.
  • Docs: dev-loop.md diagram + leak note updated; spec/plan under .claude/specs/.

No new unit tests — spawn configuration with no logic surface (spec rules out brittle package.json-grep tests).

Verification

  • bun run dev → exactly one tailwind process, direct child of the dev pid (no node grandchild, no bunx/volta wrapper)
  • Output-changing CSS edit → rebuild + css updated dev event fired (edit + revert); note: comment-only edits produce identical output and skip the rewrite (documented gotcha)
  • kill -TERM → graceful chain ran, zero tailwind/bunx survivors, port freed
  • bun run build:css → v4.2.4 (pinned), exits 0
  • Full suite 4986 pass / 0 fail; typecheck clean
  • Code review: approve; supply-chain posture improves (no runtime download of @latest)

Follow-up candidates (out of scope): prune now-dead bunx download-chatter patterns from dev-log-classifier.ts; optional existsSync(TAILWIND_BIN) guard with a "run bun install" hint.

🤖 Generated with Claude Code

markhayden and others added 2 commits June 11, 2026 20:40
Two defects from the same bunx wrapper, both observed live while
verifying #459:

- Leak: nodeSpawn('bunx', ['@tailwindcss/cli', ...]) produced a
  volta-shim -> bunx -> node chain; kill('SIGTERM') hit only the
  wrapper and the node --watch grandchild reparented to launchd,
  watching files forever.
- Drift: bunx downloaded floating @tailwindcss/cli@latest, ignoring
  the pinned ^4.2.4 devDependency.

dev.ts now spawns node_modules/.bin/tailwindcss directly under bun —
the child pid IS the tailwind process, and the version comes from the
lockfile. build:css drops bunx for the same pinning (node_modules/.bin
is on PATH in package scripts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bunx-wrapper grandchild leak is fixed by the direct local-bin
spawn; rewrite the known-gap note to record what changed and why,
and note the comment-only-edit gotcha for verifying the watch
pipeline. Update the architecture diagram line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@markhayden markhayden merged commit 296297a into main Jun 12, 2026
1 check passed
@markhayden markhayden deleted the fix/tailwind-direct-spawn branch June 12, 2026 03:11
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