Spaces:
Running
Running
Exclude Codex guardian subagent rollouts from traces and pinning
Browse filesCodex >=0.142 spawns internal "guardian" safety-judge subagents whose
rollout files share the session's cwd. The trace parser attributed them
to the user's session, polluting or blanking out the Overview digest
(codex-1 showed nothing), and the ID capture could pin a guardian's
conversation id, which would also break resume.
Flag rollouts whose session_meta says thread_source=subagent (or has a
source.subagent) and skip them in trace attribution and in
tryCaptureCodexId. Sessions already pinned to a guardian or missing
rollout self-heal on next launch: the stale pin is cleared so the real
conversation is re-captured.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- server/src/runner.js +22 -1
- server/src/traces.js +9 -1
server/src/runner.js
CHANGED
|
@@ -164,14 +164,35 @@ function tryCaptureCodexId(sessionId, workdir, sinceMs) {
|
|
| 164 |
if (!m || claimed.has(m[1])) continue;
|
| 165 |
let meta;
|
| 166 |
try { meta = JSON.parse(firstLine(c.p)); } catch { continue; }
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
update(sessionId, { codexSessionId: m[1], codexRollout: c.p });
|
| 169 |
return true;
|
| 170 |
}
|
| 171 |
return false;
|
| 172 |
}
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
function scheduleCodexCapture(session, workdir) {
|
|
|
|
|
|
|
|
|
|
| 175 |
if (session.codexSessionId || codexCapturing.has(session.id)) return;
|
| 176 |
codexCapturing.add(session.id);
|
| 177 |
const since = Date.now() - 2000;
|
|
|
|
| 164 |
if (!m || claimed.has(m[1])) continue;
|
| 165 |
let meta;
|
| 166 |
try { meta = JSON.parse(firstLine(c.p)); } catch { continue; }
|
| 167 |
+
const mp = (meta && meta.payload) || {};
|
| 168 |
+
if (mp.cwd !== workdir) continue;
|
| 169 |
+
// Skip Codex's internal guardian/subagent rollouts — they share the cwd but
|
| 170 |
+
// aren't this agent's conversation, so pinning one would break resume and
|
| 171 |
+
// the Overview digest.
|
| 172 |
+
if (mp.thread_source === 'subagent' || (mp.source && mp.source.subagent)) continue;
|
| 173 |
update(sessionId, { codexSessionId: m[1], codexRollout: c.p });
|
| 174 |
return true;
|
| 175 |
}
|
| 176 |
return false;
|
| 177 |
}
|
| 178 |
|
| 179 |
+
// A pin captured before subagents were filtered out (or one whose rollout was
|
| 180 |
+
// rotated away) may point at a guardian/missing rollout — clear it so we
|
| 181 |
+
// re-capture the real conversation on this launch.
|
| 182 |
+
function pinIsStale(session) {
|
| 183 |
+
if (!session.codexSessionId) return false;
|
| 184 |
+
const p = session.codexRollout;
|
| 185 |
+
if (!p || !fs.existsSync(p)) return true;
|
| 186 |
+
try {
|
| 187 |
+
const mp = (JSON.parse(firstLine(p)) || {}).payload || {};
|
| 188 |
+
return mp.thread_source === 'subagent' || !!(mp.source && mp.source.subagent);
|
| 189 |
+
} catch { return false; }
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
function scheduleCodexCapture(session, workdir) {
|
| 193 |
+
if (session.codexSessionId && pinIsStale(session)) {
|
| 194 |
+
session = update(session.id, { codexSessionId: undefined, codexRollout: undefined }) || session;
|
| 195 |
+
}
|
| 196 |
if (session.codexSessionId || codexCapturing.has(session.id)) return;
|
| 197 |
codexCapturing.add(session.id);
|
| 198 |
const since = Date.now() - 2000;
|
server/src/traces.js
CHANGED
|
@@ -135,7 +135,14 @@ function parseCodex(txt) {
|
|
| 135 |
let j; try { j = JSON.parse(line); } catch { continue; }
|
| 136 |
if (j.timestamp) addTs(st, j.timestamp);
|
| 137 |
const p = j.payload || {};
|
| 138 |
-
if (j.type === 'session_meta'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
if (j.type === 'response_item') {
|
| 140 |
switch (p.type) {
|
| 141 |
case 'message':
|
|
@@ -537,6 +544,7 @@ async function build() {
|
|
| 537 |
seenFiles.add(p);
|
| 538 |
const parsed = await statsFor(p, parseCodex);
|
| 539 |
if (!parsed) continue;
|
|
|
|
| 540 |
const m = path.basename(p).match(UUID_RE);
|
| 541 |
let session = m ? byCodexId.get(m[1]) : null;
|
| 542 |
if (!session && parsed.stats.cwd) {
|
|
|
|
| 135 |
let j; try { j = JSON.parse(line); } catch { continue; }
|
| 136 |
if (j.timestamp) addTs(st, j.timestamp);
|
| 137 |
const p = j.payload || {};
|
| 138 |
+
if (j.type === 'session_meta') {
|
| 139 |
+
if (p.cwd) st.cwd = p.cwd; // for cwd-fallback attribution
|
| 140 |
+
// Codex >=0.142 spawns internal "guardian" safety-judge subagents whose
|
| 141 |
+
// rollouts share the session's cwd. They aren't the user's conversation —
|
| 142 |
+
// flag them so build() skips attribution (they'd otherwise pollute or
|
| 143 |
+
// blank out the Overview digest).
|
| 144 |
+
if (p.thread_source === 'subagent' || (p.source && p.source.subagent)) st.subagent = true;
|
| 145 |
+
}
|
| 146 |
if (j.type === 'response_item') {
|
| 147 |
switch (p.type) {
|
| 148 |
case 'message':
|
|
|
|
| 544 |
seenFiles.add(p);
|
| 545 |
const parsed = await statsFor(p, parseCodex);
|
| 546 |
if (!parsed) continue;
|
| 547 |
+
if (parsed.stats.subagent) continue; // Codex guardian subagent — not the user's thread
|
| 548 |
const m = path.basename(p).match(UUID_RE);
|
| 549 |
let session = m ? byCodexId.get(m[1]) : null;
|
| 550 |
if (!session && parsed.stats.cwd) {
|