Skip to content

Commit bbafaac

Browse files
committed
fix: ignore runtime catalog mutations in supervisor watch
1 parent 126b952 commit bbafaac

2 files changed

Lines changed: 65 additions & 10 deletions

File tree

src/run.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -831,14 +831,9 @@ fn gate_codex_launches<'a, V, F>(
831831
let mut workspaces = Vec::new();
832832
let mut gated_agents = Vec::new();
833833
for launch in &plan.launch {
834-
let Some(agent) = launch
835-
.tasks
836-
.iter()
837-
.find(|target| {
838-
target.name == "agent"
839-
&& crate::hooks::command_invokes_codex(&target.command)
840-
})
841-
else {
834+
let Some(agent) = launch.tasks.iter().find(|target| {
835+
target.name == "agent" && crate::hooks::command_invokes_codex(&target.command)
836+
}) else {
842837
continue;
843838
};
844839
let spec_dir = launch.spec.path.parent().unwrap_or_else(|| Path::new("."));
@@ -1118,7 +1113,7 @@ fn up_loop_until(
11181113
mut on_report: impl FnMut(&UpReport),
11191114
) -> anyhow::Result<()> {
11201115
let (tx, rx) = channel::<()>();
1121-
let _watcher = crate::watch::watch_recursive_mutations(root, tx);
1116+
let _watcher = crate::watch::watch_catalog_declarations(root, tx);
11221117
let mut cap = FlappingCap::default();
11231118
// Carries per-id liveness across passes so a transient `pty list` flicker under load isn't
11241119
// destructively GC'd (R21c). Fresh throwaway in `up_once` — a single pass has no flicker to absorb.
@@ -1907,5 +1902,4 @@ mod tests {
19071902
assert!(!h.is_empty());
19081903
assert!(!h.contains('.'), "short name only, got {h}");
19091904
}
1910-
19111905
}

src/watch.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,42 @@ pub(crate) fn watch_recursive_mutations(
2525
Some(watcher)
2626
}
2727

28+
/// Watch only declaration inputs for the supervisor. Runtime state (PTY registry, bus, logs,
29+
/// locks, inboxes, and generated materializations) must never wake reconciliation.
30+
pub(crate) fn watch_catalog_declarations(
31+
root: &Path,
32+
tx: Sender<()>,
33+
) -> Option<notify::RecommendedWatcher> {
34+
let root = root.to_path_buf();
35+
let callback_root = root.clone();
36+
let mut watcher = notify::recommended_watcher(move |result: notify::Result<Event>| {
37+
if result.is_ok_and(|event| {
38+
is_mutation(&event)
39+
&& event
40+
.paths
41+
.iter()
42+
.any(|path| is_declaration_path(&callback_root, path))
43+
}) {
44+
let _ = tx.send(());
45+
}
46+
})
47+
.ok()?;
48+
watcher.watch(&root, RecursiveMode::Recursive).ok()?;
49+
Some(watcher)
50+
}
51+
52+
fn is_declaration_path(root: &Path, path: &Path) -> bool {
53+
let rel = path.strip_prefix(root).unwrap_or(path);
54+
let mut components = rel.components();
55+
if matches!(
56+
components.next().and_then(|c| c.as_os_str().to_str()),
57+
Some("_templates")
58+
) {
59+
return true;
60+
}
61+
path.file_name().and_then(|n| n.to_str()) == Some("agent.kdl")
62+
}
63+
2864
fn is_mutation(event: &Event) -> bool {
2965
matches!(
3066
event.kind,
@@ -60,6 +96,31 @@ mod tests {
6096
}
6197
}
6298

99+
#[test]
100+
fn declaration_filter_ignores_runtime_state() {
101+
let root = Path::new("/catalog");
102+
assert!(is_declaration_path(
103+
root,
104+
Path::new("/catalog/team/agent.kdl")
105+
));
106+
assert!(is_declaration_path(
107+
root,
108+
Path::new("/catalog/_templates/base.kdl")
109+
));
110+
assert!(!is_declaration_path(
111+
root,
112+
Path::new("/catalog/pty/session.json")
113+
));
114+
assert!(!is_declaration_path(
115+
root,
116+
Path::new("/catalog/bus/inbox/msg")
117+
));
118+
assert!(!is_declaration_path(
119+
root,
120+
Path::new("/catalog/team/rendered.kdl")
121+
));
122+
}
123+
63124
#[cfg(target_os = "linux")]
64125
#[test]
65126
fn linux_reads_are_silent_but_real_mutations_wake() {

0 commit comments

Comments
 (0)