10 KiB
APP-3945: Channel-aware Warp home watching Technical Spec
Problem
Warp's hot-reload behavior for user-managed files depends on several consumers observing filesystem changes from the current channel's Warp home. This work centralizes those updates behind WarpDataDirectoryWatcher, but it must also preserve a platform-specific requirement: public settings live under config_local_dir() while most other Warp home content lives under data_dir().
The technical problem is to keep one Warp-specific watcher abstraction while:
- filtering
.warp*/worktreesonly for the data-directory tree - watching
config_local_dir()in addition todata_dir()when those roots differ - preserving the existing downstream event contracts used by
WarpConfig, Warp home MCP watching, and Warp home skill watching - avoiding new generic filtering APIs in
repo_metadata::DirectoryWatcher
Relevant code
app/src/warp_data_directory_watcher.rs— Warp-specific singleton watcher that owns aBulkFilesystemWatcher, registers Warp home roots, and emitsWarpDataDirectoryWatcherEvent::FilesChanged.app/src/lib.rs— startup wiring that prepares the Warp watch roots before registeringWarpDataDirectoryWatcher.app/src/user_config/native.rs—WarpConfigsubscription that maps watcher updates toThemes,Workflows,LaunchConfigs,TabConfigs, andSettingsevents.app/src/settings/init.rs— settings-file hot-reload pipeline that reacts toWarpConfigUpdateEvent::Settings.app/src/ai/mcp/file_mcp_watcher.rs— Warp home MCP watcher subscriber that depends onWarpDataDirectoryWatcher.app/src/ai/mcp/mod.rs— helper that resolves the Warp MCP home config path through the shared Warp data directory helpers.app/src/ai/skills/file_watchers/skill_watcher.rs— Warp home skill watcher subscriber that depends onWarpDataDirectoryWatcher.app/src/ai/skills/file_watchers/utils.rs— skill path parsing helpers, including the Warp-home special case.app/src/ai/skills/skill_utils.rs— helper for resolving a skill root from a changed file path.crates/ai/src/skills/skill_provider.rs— provider/scope classification for channel-aware Warp home skill paths.crates/integration/src/test/settings_file_hot_reload.rs— end-to-end settings hot-reload coverage.
Current state
WarpDataDirectoryWatcher is a Warp-specific singleton that owns its own BulkFilesystemWatcher, similar to HomeDirectoryWatcher. It does not depend on DirectoryWatcher, and it does not require any per-directory filter plumbing in repo_metadata.
At startup:
- setup code prepares
data_dir()beforeWarpDataDirectoryWatcheris registered WarpDataDirectoryWatcherregistersdata_dir()recursively with aWatchFilterthat excludes<data_dir>/worktrees- setup code prepares
config_local_dir()when it differs fromdata_dir() WarpDataDirectoryWatcherregistersconfig_local_dir()recursively when it differs fromdata_dir()
The watcher receives BulkFilesystemWatcherEvent values, converts them into RepositoryUpdate, and emits WarpDataDirectoryWatcherEvent::FilesChanged directly. It does not apply a second worktree filter after registration-time filtering.
WarpConfig subscribes to that event stream and reloads themes, workflows, launch configs, and tab configs on background tasks when the update touches the relevant paths. Settings continue to flow through WarpConfigUpdateEvent::Settings.
FileMCPWatcher subscribes to the same singleton for Warp home MCP updates while continuing to use the existing home-directory and repository watching paths for non-Warp providers. The duplicated startup parse path and the duplicated single-config incremental update path are now shared with the non-Warp logic.
SkillWatcher subscribes to the same singleton for Warp home skill updates while continuing to use the existing home-directory and repository watching paths for non-Warp providers and project skills. The initial Warp home skill load and repository-scan load now share the same directory-read helper.
Because Warp home skills now live under the channel-aware data_dir()/skills, helper code also needs to recognize that path when determining provider, scope, and enclosing skill directory.
Chosen design
1. Dedicated Warp watcher ownership
WarpDataDirectoryWatcher owns a BulkFilesystemWatcher directly instead of layering on top of DirectoryWatcher.
This keeps the abstraction boundary simple:
- project repositories still use
DirectoryWatcher - Warp-owned home/config directories use
WarpDataDirectoryWatcher
That separation avoids broadening the generic repo watcher API for a Warp-specific use case.
2. Watch root preparation and registration
Each Warp-owned root is prepared before watcher registration, but that preparation happens outside the watcher constructor:
- call
create_dir_all()during startup/setup - keep watcher registration separate from root creation
- log failures before registration rather than implicitly recovering inside watcher construction
data_dir() is registered with a watcher-level WatchFilter that excludes <data_dir>/worktrees.
config_local_dir() is registered with WatchFilter::accept_all() when it is distinct from data_dir().
3. Update normalization
BulkFilesystemWatcherEvent is converted into RepositoryUpdate so existing subscribers can keep using the same helper logic and event vocabulary.
filter_repository_update() is retained as a downstream helper to:
- keep only the paths relevant to a downstream consumer
- convert cross-boundary moves into add/delete updates when a move crosses the watched prefix boundary
4. Downstream consumers
WarpConfig remains subscription-based and does not reintroduce a direct notify watcher.
It continues to:
- reload themes, workflows, launch configs, and tab configs when updates touch the relevant directories
- perform those file-backed reloads via
ctx.spawn(...)so disk reads happen off the model thread - emit
WarpConfigUpdateEvent::Settingswhen updates touchuser_preferences_toml_file_path()
FileMCPWatcher subscribes to Warp watcher events for data_dir()/.mcp.json and keeps the existing logic for non-Warp providers. Warp remains on the Warp-specific watcher source, but shared helpers now cover:
- the startup parse path for a single config file
- the single-config incremental update path used by both Warp and non-Warp providers
SkillWatcher subscribes to Warp watcher events for data_dir()/skills and keeps the existing logic for non-Warp home providers and project repositories. The Warp home initialization path and repository scan path share the same helper for reading skill directories and emitting updates.
5. Channel-aware helper paths
The helper path logic is updated so Warp home skills and MCP config resolve through the active channel's data directory:
- Warp MCP home config resolves to
warp_data_mcp_config_file_path() - Warp home skills resolve to
data_dir()/skills - provider and scope classification recognize those paths as Warp home paths rather than project paths
End-to-end flow
- Warp startup prepares
data_dir()and, when distinct,config_local_dir()before constructingWarpDataDirectoryWatcher. WarpDataDirectoryWatcherregistersdata_dir()with a watcher-level filter that excludes<data_dir>/worktrees.- If
config_local_dir()differs fromdata_dir(),WarpDataDirectoryWatcherregisters that root with no extra filter. - The underlying
BulkFilesystemWatcheremitsBulkFilesystemWatcherEvent. WarpDataDirectoryWatcherconverts that event intoRepositoryUpdateand emitsFilesChanged(update).WarpConfig,FileMCPWatcher, andSkillWatcherinspect the update and react only when their owned paths were touched.- For themes, workflows, launch configs, and tab configs,
WarpConfigreloads from disk on background tasks before applying the new state on the model thread. - For
settings.toml,WarpConfigemitsWarpConfigUpdateEvent::Settings, andsettings::init()reloads public settings from disk.
Risks and mitigations
- Distinct-root platforms regress settings hot reload.
- Mitigation: register
config_local_dir()separately when it differs fromdata_dir().
- Mitigation: register
- Worktree events leak back into Warp home consumers.
- Mitigation: apply a watcher-level filter to
data_dir()and let downstream consumers do any additional prefix filtering they need.
- Mitigation: apply a watcher-level filter to
- Fresh installs fail to register watchers because the root directory is missing.
- Mitigation: prepare each watch root during startup before registration.
- Path comparisons miss updates because watcher event paths and logical config paths may differ by symlink or canonical form.
- Mitigation:
WarpConfigcontinues checking both raw and canonicalized paths inupdate_touches_dir()andupdate_touches_path().
- Mitigation:
- Warp home skill or MCP paths are misclassified as project paths.
- Mitigation: centralize Warp-home path handling in the MCP and skills helper utilities.
Testing and validation
cargo test -p warp --lib filter_repository_update_by_prefix_keeps_only_matching_paths- validates the downstream filtering helper that skills consumers rely on
cargo test -p integration --test integration settings_file_hot_reload -- --nocapture- verifies the end-to-end settings hot-reload behavior by rewriting
settings.tomland asserting in-memory settings updates
- verifies the end-to-end settings hot-reload behavior by rewriting
- Code review validation:
data_dir()is watched with theworktreesexclusionconfig_local_dir()is watched only when distinctWarpDataDirectoryWatcherowns its own filesystem watcher instead of extendingDirectoryWatcher- watch-root preparation happens before watcher registration instead of inside the watcher constructor
- Warp home MCP and skill helpers resolve through the channel-aware data directory
Follow-ups
- Add a focused test for
WarpDataDirectoryWatcherroot registration whendata_dir()andconfig_local_dir()differ. - Consider whether the remaining Warp-home helper changes in the MCP and skills layers can be reduced further without regressing path classification.