本文へ移動
cccskills
無料GitHub で公開

parcel

Kolu's project-wide default for filesystem monitoring is `@parcel/watcher`. Reach for this skill when adding or modifying any code that watches files or directories — recursive subtree watching, single-file observation, fs.watch alternatives, chokidar replacement, inotify/FSEvents/watchman backend selection, ignore globs, watcher debouncing, or refcounted shared subscriptions. Covers backend dispatch, the watchman invocation path, ignore handling, post-install reconciliation, and the failure modes Kolu's logger surfaces.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md13.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

@parcel/watcher integration

@parcel/watcher is Kolu's default filesystem watcher. Reach for it instead of chokidar, raw fs.watch, or hand-rolled polling whenever a feature needs to observe a directory subtree. Today's only consumer is the working-tree watcher (packages/integrations/git/src/working-tree-watcher.ts) — the git-dir watchers (head-watcher, reflog-watcher, index-watcher) use plain fs.watch via kolu-io's refcounted-dir-watcher.ts because they target a single known file inside .git/, where parcel-watcher's recursive model would be overkill. New fs-monitoring code should default to parcel-watcher unless it has a similarly narrow target.

Backend dispatch

Source: node_modules/.pnpm/@parcel+watcher@2.5.6/node_modules/@parcel/watcher/src/Backend.cc:30-69.

Kolu pins the OS-native backend explicitly (inotify / fs-events / windows, chosen off process.platform — see PARCEL_BACKEND in working-tree-watcher.ts). It does not use backend: "default". The pin skips the auto-dispatch order below — most importantly the WatchmanBackend probe, whose per-subscribe popen leaks a zombie on watchman-less hosts (see next section). If you write a new parcel consumer, pin the backend the same way.

backend: "default" would select in this order, first match wins:

  1. FSEvents on macOS — native recursive, one stream per repo.
  2. WatchmanBackend if WatchmanBackend::checkAvailable() returns true.
  3. WindowsBackend on Windows — native recursive ReadDirectoryChangesW.
  4. InotifyBackend on Linux — one inotify slot per non-ignored directory.
  5. KqueueBackend on BSD.
  6. BruteForceBackend — periodic full-tree stat; the fallback fallback.

So under "default" on Linux, watchman is probed (and preferred if reachable) before inotify; on macOS watchman is never used (FSEvents wins). The explicit pin lands directly on step 1/3/4 for the platform and never runs step 2's probe.

How parcel invokes watchman

Source: src/watchman/WatchmanBackend.cc.

  1. checkAvailable() (line 107) just calls watchmanConnect().

  2. getSockPath() (line 43) does the only watchman binary invocation:

    popen("watchman --output-encoding=bser get-sockname 2>/dev/null", "r")
    

    then parses BSER output for the sockname field. If WATCHMAN_SOCK env var is set, that wins and the binary isn't run at all.

    ⚠ Zombie leak — why Kolu pins the backend (juspay/kolu#1691). On a host without watchman, this popen forks /bin/sh, the watchman exec fails, and parcel's error path returns without pclose() — so the sh is never wait()ed. popen bypasses child_process, so Node's libuv can't reap it either: it lingers as a zombie forever, one per subscribe. Free on CPU/RAM but a long-lived daemon (a remote-bound padi serving a full e2e run) accumulates dozens and the event loop drags. Pinning the native backend (above) skips checkAvailable() entirely, so the popen never runs. Verified: per-subscribe leak → 0.

  3. From there it's a Unix-domain socket carrying BSER-encoded JSON. No more subprocess spawns.

Commands (BSER arrays sent over the socket):

SourceCommandPurpose
watchmanWatch (line 100)["watch", "/abs/dir"]start tracking
clock (line 230)["clock", "/abs/dir"]get baseline clock token
subscribe (line 281)["subscribe", "/abs/dir", "parcel-<ptr>", {fields,since,expression}]start receiving events
unsubscribe (line 330)["unsubscribe", "/abs/dir", id]tear down

Subscription IDs are "parcel-" + hex(watcher-pointer). Event mapping in handleFiles (line 137):

  • new && exists → create
  • exists && !S_ISDIR(mode) → update
  • !new && !exists → remove

Ignore globs are translated to a watchman expression ["not", ["anyof", ["dirname", rel], ...]] only for ignores that are direct subpaths of the watched root (line 300). Glob-style ignores like **/node_modules are filtered client-side after events arrive — pass them in the ignore option but don't expect watchman-side filtering.

Cookie files (.watchman-cookie-*)

Written by the watchman daemon, not parcel-watcher. The daemon drops these files inside watched dirs to verify it can observe its own writes (watch/query commands trigger the dance). Normally ephemeral. Stragglers mean the daemon was killed mid-handshake. Add .watchman-cookie-* to .gitignore if watchman is in use.

Kolu's runtime status

As of #788, Kolu does not ship watchman with the production binary. The nix run wrapper (default.nix:156) only adds nodejs git gh to PATH — so even under backend: "default" the watchman probe would always fail and parcel would fall through to inotify on Linux / FSEvents on macOS. Since #1692, Kolu doesn't rely on that fall-through: it pins the native backend, so checkAvailable() (and its leaking popen) never runs at all. Issue #788 tracks the watchman integration work; if it lands, the pin is where you'd re-enable watchman deliberately (with a WATCHMAN_SOCK that avoids the probe).

Kolu's wrapper invariants

packages/integrations/git/src/working-tree-watcher.ts:

  • Refcounted shared singleton per repoRoot — N callers → one parcel subscription, listener-side filtering by optional filePath.
  • Trailing-edge debounce at WATCHER_DEBOUNCE_MS (150ms) coalesces bursts into one fire per listener.
  • Hard-coded IGNORE_GLOBS — .git, node_modules, dist, build, target, .next, .turbo, .cache, .parcel-cache, .kolu-dev, .kolu-state, .DS_Store. Not gitignore-aware. Over-firing on user build outputs is absorbed by the snapshot-equality check in streamSnapshots(...) upstream.
  • Post-install reconciliation tick (line 176) — parcelSubscribe is async. Filesystem mutations between subscribe() call and parcel resolving are invisible to parcel; the streaming endpoint already yielded its initial snapshot. The reconciliation fires every current listener once parcel is ready, so consumers re-read state and catch the missed window. Without this the client sees a stale view that no future event corrects.
  • cancelled guard on the .then — if the last subscriber unsubscribed before parcel resolved, late-unsubscribe the AsyncSubscription instead of storing it.
  • Per-repoRoot call chain (parcelCallChains) — every subscribe and unsubscribe for a repo runs one at a time, in issue order. Never let two parcel calls for the same directory overlap; see the hazard below.

What Kolu logs

Watcher lifecycle through Logger (kolu-shared). Grep these strings to verify the watcher came up in production:

  • info git: working-tree watcher installed ← parcel resolved successfully
  • info git: working-tree watcher retired ← last subscriber gone
  • error git: working-tree watcher install failed ← parcel subscribe() rejected
  • error git: working-tree watcher callback error ← parcel reported event-stream error
  • error git: working-tree late-unsubscribe failed
  • error git: working-tree unsubscribe failed
  • error git: working-tree listener threw

Individual filesystem events are not logged — too noisy. The bucket-and- debounce path swallows event paths silently.

Failure modes worth knowing

  1. Container/WSL2 bind mounts — inotify/FSEvents may be unavailable. Nuance since the backend pin (#1692): BruteForceBackend (~1s polling) is parcel's compile-time-last-resort backend — under "default" it's reached only when no earlier backend is compiled in, not when a compiled one fails to construct at runtime. On Kolu's Linux build INOTIFY is compiled, so "default" selects InotifyBackend (after the failed watchman probe), and a bind mount that can't inotify makes both "default" and the pinned "inotify" fail identically (error git: working-tree watcher install failed) — the pin forgoes no polling path there. On the three targets the pin is behaviorally identical to "default"'s post-probe outcome, minus the leak. On an untargeted platform (not linux/darwin/win32) the pin throws at module init (unsupported platform …) rather than degrade to a silent poller — kolu targets exactly those three, and the repo's fail-fast philosophy forbids a graceful-degradation fallback. (If a BSD/other target ever became real, map it to its native backend — kqueue etc. — not to a poller.) Net: leak-free everywhere, and no platform silently degrades.

  2. Linux inotify slot exhaustion — kernel default is fs.inotify.max_user_watches=8192. A typical Kolu repo uses ~500–2000 slots; multiple worktrees compound. Watchman amortizes this across one daemon (#788).

  3. detect-libc glibc/musl picker runs on require('@parcel/watcher') (index.js:5). The Nix build needs the matching native binary in node_modules — confirm @parcel/watcher-linux-x64-glibc is present under node_modules/.pnpm/.

  4. Overlapping subscribe/unsubscribe for the same key silently kills the watcher (juspay/kolu#2065) — the worst failure mode here, because nothing reports it. Parcel keys its process-global Watcher registry (Watcher::getShared) AND its backend subscription set (Backend::watch / Backend::unwatch) on (dir, ignorePaths, ignoreGlobs), and runs each call's backend half on a libuv threadpool thread. Two overlapping calls for the same key therefore land in arbitrary order, and one order loses: the new subscribe finds the retiring watcher still registered (equal by key) and installs no OS watches, then the retiring unsubscribe tears the existing ones down. The new subscription resolves, holds its callbacks, logs watcher installed, and receives nothing, forever.

    Measured on @parcel/watcher@2.5.6: an un-awaited unsubscribe() followed by an immediate re-subscribe() of the same dir + ignore set produced a dead watcher in 18/25 idle runs, and 10/10 when the libuv pool was saturated (at rebuild delays from 15ms to 200ms). Awaiting the teardown first: 0/25 and 0/10. On an idle process the teardown usually wins on its own, which is why this hides in single-scenario runs and only bites a loaded parallel suite.

    Rule: serialize a directory's parcel calls (kolu does this in sequenceParcelCall). Pinned by working-tree-watcher.churn.test.ts, which loads the threadpool to force the lossy ordering — on linux/inotify only, for the reason below.

  5. macOS: delivery can run ~15s behind, and parcel's stream is the reason. Parcel's startStream (src/macos/FSEventsBackend.cc) creates the stream without kFSEventStreamCreateFlagNoDefer, so the first event's delivery is deferred rather than immediate. On a volume under constant churn the defer window collapses to the daemon's maximum coalescing window. Measured on kolu's darwin CI box (ci@petit, macOS 26.5.2, juspay/kolu#2175): first event 14.2s after the write, then batches every 15.0s, 53/53 events delivered — nothing lost, everything late. fs.watch in the same process, on the same directory, answered in 1.3s; libuv passes NoDefer. That box's fseventsd was also 21 days up, 6.5 GB resident, ~190% CPU — worth checking before concluding anything about a healthy mac.

    Two consequences. A test may not gate on a parcel event arriving on darwin (fs-watch-delivery.testlib.ts is the one flag that says so; #2175 also records a rarer case where a post-churn subscription delivered nothing at all for 60s+, reproduced with no kolu code in the loop). And on a macOS host whose filesystem is this busy, kolu's own Code tab is that far behind — worth remembering before chasing a "stale git status" report as a kolu bug.

  6. dontFixup = true in default.nix skips patchELF on the native .node binary. Today the @parcel/watcher binary loads its own libstdc++ via fallback paths and works, but if a future parcel-watcher version pulls in a harder dynamic-link requirement, expect to revisit this.

Quick references

  • Backend dispatch: node_modules/.pnpm/@parcel+watcher@2.5.6/node_modules/@parcel/watcher/src/Backend.cc:30
  • Watchman invocation: node_modules/.pnpm/@parcel+watcher@2.5.6/node_modules/@parcel/watcher/src/watchman/WatchmanBackend.cc:43
  • Kolu wrapper: packages/integrations/git/src/working-tree-watcher.ts
  • Composed watcher API: packages/integrations/git/src/repo-change.ts
  • Watchman integration issue: juspay/kolu#788
  • Streaming endpoints that consume the watcher: packages/server/src/router.ts (onStatusChange, onDiffChange, onListAllChange, onReadFileChange)

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

blog-post

無料

Write a kolu blog post grounded in the real build history — mine the Claude Code session logs behind a feature for the actual story (for large efforts, fan out over the transcripts with an ultracode workflow), draft it in the author's voice, and wire it into the Astro site. Use when asked to write a blog post or engineering essay about something that was built, especially one that should read as a narrative of what actually happened rather than invented marketing copy.

日本語の概要は準備中です。原文の説明を表示しています。

juspay/kolu812026年10月11日 更新

ci

無料

Run this repo's CI end-to-end — the kolu-specific procedure on top of the odu runner: the odu MCP front door, banned opt-out flags, mandatory two-platform coverage, venue-pool leasing, fail-fast surfacing, e2e timing evidence, and the green-gate. Triggers on "run CI", "drive CI", "re-run the pipeline", "close the red check", "warm the pool". Runner mechanics live in the `/odu` skill.

日本語の概要は準備中です。原文の説明を表示しています。

juspay/kolu812026年10月11日 更新

Launch the kolu dev server on two RANDOM free ports so it never collides with the running production `kolu.service`, remember the ports for the rest of the session, and tear down only the dev instance. Load before running the app locally — for evidence capture, driving a live kolu with the chrome-devtools MCP, or any `just dev` you'd otherwise run by hand. Triggers on "run kolu locally", "launch the dev server", "boot kolu", "drive a live kolu", "start the app to screenshot it", or before pointing chrome-devtools at a local kolu.

日本語の概要は準備中です。原文の説明を表示しています。

juspay/kolu812026年10月11日 更新

diataxis

無料

Classify, write, and audit documentation per Diátaxis (diataxis.fr) — the four-quadrant method (tutorials · how-to guides · reference · explanation). Encodes the compass as a decision procedure, each quadrant's contract as hard rules, and an executable mixed-mode audit. Project-agnostic; the per-repo doc-location mapping lives in a repo rule of the project's own (kolu: `.apm/instructions/diataxis.instructions.md`). Use when writing any user-facing doc, structuring a docs site, or auditing existing docs for mixed modes.

日本語の概要は準備中です。原文の説明を表示しています。

juspay/kolu812026年10月11日 更新

evidence

無料

Produce visual PR evidence — a screenshot or video — whenever a change has on-screen impact. Tests are never a substitute; a change can be backend by cause and visible by effect. Capture via the project's e2e harness on a pu box, or by driving a live kolu with the chrome-devtools MCP; host on a GitHub release and post a `## Evidence` comment. Triggers on "post evidence", "screenshot the change", "record a video of this", "show it working", "prove it", or finishing any change whose effect is visible on screen.

日本語の概要は準備中です。原文の説明を表示しています。

juspay/kolu812026年10月11日 更新

Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.

日本語の概要は準備中です。原文の説明を表示しています。

juspay/kolu812026年10月11日 更新

juspay のスキルをすべて見る

このスキルの問題を報告する