Summary
Two set_component_file writes to a component's config.yaml arriving in quick succession (observed: 9ms apart) can result in the second write never being observed by the component's OptionsWatcher — the live config keeps the first write's values until some later event touches the file.
Evidence
Observed while developing an integration test for the static plugin (branch kris/static-cache-headers): an alternating pass/fail pattern where every config update issued immediately after a fast-succeeding previous update was lost. Harper log excerpt showing the paired writes:
2026-07-09T23:48:09.802Z [main/0] [info] [operation]: { operation: 'set_component_file', project: 'static-cache-headers', file: 'config.yaml' }
2026-07-09T23:48:09.811Z [main/0] [info] [operation]: { operation: 'set_component_file', project: 'static-cache-headers', file: 'config.yaml' }
The first write was applied (served headers changed); the second never was, across a 15s polling window.
Analysis
components/OptionsWatcher.ts#handleChange itself is sound — every chokidar event triggers a fresh readFile, so last-write-wins on contents. The loss is at the chokidar/inotify layer: a write landing while the watch is being re-established (e.g. after an atomic replace/rename from the prior write) emits no event, and nothing self-heals afterward because no later event arrives.
Impact
Low-frequency but user-visible: two quick config saves (studio, deploy tooling, scripts) can leave the component running the older config with no error. Same seam presumably affects any watched config, not just static.
Suggested direction
- Debounced re-read after the last observed event (cheap self-heal), or a slow periodic mtime check as a safety net, in
OptionsWatcher.
- Alternatively have
set_component_file nudge the watcher (it knows the write happened).
— found while testing #1746's static-plugin follow-up; reported by Claude (Opus 4.8) with Kris
Summary
Two
set_component_filewrites to a component'sconfig.yamlarriving in quick succession (observed: 9ms apart) can result in the second write never being observed by the component'sOptionsWatcher— the live config keeps the first write's values until some later event touches the file.Evidence
Observed while developing an integration test for the static plugin (branch
kris/static-cache-headers): an alternating pass/fail pattern where every config update issued immediately after a fast-succeeding previous update was lost. Harper log excerpt showing the paired writes:The first write was applied (served headers changed); the second never was, across a 15s polling window.
Analysis
components/OptionsWatcher.ts#handleChangeitself is sound — every chokidar event triggers a freshreadFile, so last-write-wins on contents. The loss is at the chokidar/inotify layer: a write landing while the watch is being re-established (e.g. after an atomic replace/rename from the prior write) emits no event, and nothing self-heals afterward because no later event arrives.Impact
Low-frequency but user-visible: two quick config saves (studio, deploy tooling, scripts) can leave the component running the older config with no error. Same seam presumably affects any watched config, not just
static.Suggested direction
OptionsWatcher.set_component_filenudge the watcher (it knows the write happened).— found while testing #1746's static-plugin follow-up; reported by Claude (Opus 4.8) with Kris