Skip to content

feat: use FSEvents for macOS file-watch triggers - #1753

Merged
d0ugal merged 1 commit into
mainfrom
d0ugal/graith/issue-1750-fsevents-d815fd6e
Jul 27, 2026
Merged

feat: use FSEvents for macOS file-watch triggers#1753
d0ugal merged 1 commit into
mainfrom
d0ugal/graith/issue-1750-fsevents-d815fd6e

Conversation

@d0ugal

@d0ugal d0ugal commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a daemon-internal file-watch backend interface and select FSEvents automatically on darwin && cgo, with fsnotify preserved everywhere else.
  • Use one recursive FSEvents stream per watch binding while keeping matcher, ignore, debounce, retry, lifecycle, and trigger delivery semantics shared.
  • Conservatively scan FSEvents coalesced/lossy subtrees, directory renames, moved-in trees, and missing-path subtree events, and document FSEvents latency, coalescing, and path-based rename limits.

Resource evidence

  • On macOS/cgo, TestFSEventsBackendUsesSingleRecursiveStream creates a nested worktree and a broad **/*.go watch with watch_max_directories = 1; the binding stays healthy with watch_backend=fsevents, one recorded watch path, and daemon watch cost 1.
  • The fsnotify backend remains directory-registration based, so non-FSEvents backends still charge their existing per-directory or macOS kqueue descriptor estimate.

Scope notes

Fixes #1750

Test plan

  • go test -v ./internal/daemon -run 'Test(FSEventsBackend|HandleWatchEvent_(CoalescedSubtreeScan|LossySubtreeScanNotesEligibleDirectory|LossyMissingSubtreeNotesEligibleDirectory|RenamedDirectoryScansSubtree|PreciseDirectoryScanDoesNotNoteEmptyEligibleSubtree)|FileWatchBudgetDegradesAndReleases|WatcherDiagnosticAttribution)$' -count=1
  • go test -v ./internal/daemon -run 'TestFSEventsBackend(UsesSingleRecursiveStream|EndToEndNestedCreate|DirectoryRenameRequestsSubtreeScan)$' -count=1
  • go test ./internal/cli -run 'TestCheckWatcherResources|TestTrigger' -count=1
  • go test ./internal/config -count=1
  • go test ./internal/protocol -count=1
  • go build ./cmd/graith
  • make lint-only
  • git diff --check
  • CI passes on latest push

Note: before the rebase onto #1752, the same focused daemon suite also passed under -race. A full local go test ./internal/daemon -count=1 attempt was interrupted after several minutes in unrelated lifecycle coverage without surfacing a concrete failure; CI remains the authoritative full-suite run.

github-actions Bot added a commit that referenced this pull request Jul 27, 2026
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

📸 Docs preview

Preview screenshots for this PR were cleaned up after it was closed.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

📊 Test coverage

Component Coverage Δ vs base
Go (./...) 80.5% +0.0%
Swift (gui/shared) not run (no gui/ changes)
Per-file Go coverage (most uncovered first) — click to expand
File Coverage Statements Δ vs base
internal/libghosttydeps/generate.go 11.4% 55/482 +0.0%
internal/cipolicy/artifact.go 74.2% 741/999 +0.0%
internal/cipolicy/p11_js_surface.go 73.0% 316/433 +0.0%
internal/config/config.go 92.6% 1098/1186 +0.0%
internal/cipolicy/plan.go 82.9% 372/449 +0.0%
internal/cipolicy/validate.go 79.2% 262/331 +0.0%
internal/libghosttyarchive/archive.go 73.7% 185/251 +0.0%
internal/cibaseline/inventory.go 84.7% 355/419 +0.0%
cmd/docspreview/main.go 39.2% 29/74 +0.0%
internal/agent/transcript/codex.go 85.7% 240/280 +0.0%
internal/docspreview/docspreview.go 86.7% 255/294 +0.0%
internal/agent/transcript/claude.go 81.2% 155/191 +0.0%
internal/cipolicy/io.go 56.2% 41/73 +0.0%
internal/sandbox/nono.go 82.7% 153/185 +0.0%
cmd/docsdiff/main.go 91.0% 314/345 +0.0%
internal/store/store.go 84.0% 163/194 +0.0%
internal/cipolicy/build.go 89.8% 229/255 +0.0%
internal/libghosttydeps/cmd/main.go 0.0% 0/26 +0.0%
cmd/architecturecheck/main.go 0.0% 0/25 +0.0%
internal/architecture/check.go 88.6% 171/193 +0.0%
internal/config/trigger.go 93.4% 310/332 +0.0%
internal/sandbox/safehouse.go 74.7% 65/87 +0.0%
internal/testutil/graith.go 63.3% 38/60 +0.0%
cmd/cipolicy/main.go 75.9% 66/87 +0.0%
internal/version/update.go 71.6% 53/74 +0.0%
internal/git/worktree.go 82.4% 89/108 +0.0%
internal/sandbox/denials.go 88.8% 151/170 +0.0%
internal/config/paths.go 71.4% 45/63 +0.0%
internal/capabilities/capabilities.go 91.9% 192/209 +0.0%
internal/cipolicy/credential.go 66.0% 33/50 +0.0%
internal/docspreview/github.go 87.4% 118/135 +0.0%
internal/protocol/manifest.go 78.5% 62/79 +0.0%
internal/executablepin/pin_linux.go 62.5% 25/40 +0.0%
internal/scenariofile/scenariofile.go 94.1% 240/255 +0.0%
internal/libghosttydeps/lock.go 86.3% 88/102 +0.0%
cmd/cibaseline/main.go 71.7% 33/46 +0.0%
internal/sandbox/why.go 80.9% 55/68 +0.0%
internal/cipolicy/manifest.go 90.2% 101/112 +0.0%
internal/libghosttydeps/verify.go 84.7% 61/72 +0.0%
internal/agent/transcript/render.go 87.7% 64/73 +0.0%
internal/atomicfile/atomicfile.go 81.6% 40/49 +0.0%
internal/ignore/ignore.go 82.7% 43/52 +0.0%
internal/config/watcher.go 78.9% 30/38 +0.0%
internal/sessionlabel/label.go 85.2% 46/54 +0.0%
cmd/libghosttyarchive/main.go 74.1% 20/27 +0.0%
internal/git/branch.go 82.1% 32/39 +0.0%
internal/agent/transcript/usage.go 88.5% 46/52 +0.0%
internal/protocol/messages.go 78.6% 22/28 +0.0%
internal/config/scenario.go 72.2% 13/18 +0.0%
internal/detector/detector.go 95.2% 100/105 +0.0%
internal/agent/transcript/scanner.go 91.5% 43/47 +0.0%
internal/config/render.go 92.7% 51/55 +0.0%
internal/sandbox/kernel_linux.go 0.0% 0/4 +0.0%
internal/sandbox/sandbox.go 84.0% 21/25 +0.0%
internal/git/maintenance.go 83.3% 15/18 +0.0%
internal/testutil/git.go 93.5% 43/46 +0.0%
internal/agent/transcript/transcript.go 92.3% 24/26 +0.0%
internal/git/git.go 97.5% 79/81 +0.0%
internal/output/output.go 89.5% 17/19 +0.0%
internal/scenariofile/protocol_config.go 97.7% 84/86 +0.0%
internal/tools/tools.go 96.0% 48/50 +0.0%
cmd/covreport/main.go 97.1% 34/35 +0.0%
internal/covreport/covreport.go 98.7% 77/78 +0.0%
internal/git/username.go 96.0% 24/25 +0.0%
internal/hookoutput/hookoutput.go 90.0% 9/10 +0.0%
internal/libghosttydeps/render.go 94.1% 16/17 +0.0%
internal/processidentity/processidentity.go 93.3% 14/15 +0.0%
internal/protocol/frame.go 95.5% 21/22 +0.0%
internal/testprocess/testprocess.go 90.9% 10/11 +0.0%
internal/agent/agent.go 100.0% 40/40 +0.0%
internal/agent/transcript/limits.go 100.0% 10/10 +0.0%
internal/config/template.go 100.0% 23/23 +0.0%
internal/config/trigger_template.go 100.0% 16/16 +0.0%
internal/cronx/cronx.go 100.0% 24/24 +0.0%
internal/scenariofile/policy.go 100.0% 64/64 +0.0%

Base coverage: Go 80.5%. Per-file figures are statement coverage. Swift shows an absolute % (no delta) to keep macOS CI light. Updated on every push.

@d0ugal
d0ugal force-pushed the d0ugal/graith/issue-1750-fsevents-d815fd6e branch 2 times, most recently from 7be89d5 to ae7583a Compare July 27, 2026 11:02
github-actions Bot added a commit that referenced this pull request Jul 27, 2026
@d0ugal
d0ugal force-pushed the d0ugal/graith/issue-1750-fsevents-d815fd6e branch from ae7583a to 60fa427 Compare July 27, 2026 11:07
github-actions Bot added a commit that referenced this pull request Jul 27, 2026
@d0ugal
d0ugal force-pushed the d0ugal/graith/issue-1750-fsevents-d815fd6e branch from 60fa427 to c40b370 Compare July 27, 2026 11:22
github-actions Bot added a commit that referenced this pull request Jul 27, 2026
@d0ugal
d0ugal force-pushed the d0ugal/graith/issue-1750-fsevents-d815fd6e branch from c40b370 to 5aa4db5 Compare July 27, 2026 11:30
@d0ugal
d0ugal marked this pull request as ready for review July 27, 2026 11:34
@d0ugal
d0ugal force-pushed the d0ugal/graith/issue-1750-fsevents-d815fd6e branch from 5aa4db5 to a52b58d Compare July 27, 2026 11:35
@d0ugal
d0ugal enabled auto-merge (rebase) July 27, 2026 11:35
github-actions Bot added a commit that referenced this pull request Jul 27, 2026
@d0ugal
d0ugal merged commit 1dc0001 into main Jul 27, 2026
48 checks passed
@d0ugal
d0ugal deleted the d0ugal/graith/issue-1750-fsevents-d815fd6e branch July 27, 2026 11:58
github-actions Bot added a commit that referenced this pull request Jul 27, 2026
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.

feat: use FSEvents for recursive file watching on macOS

1 participant