From 873bb16d0ca0d3296dcd8317b680ff2ccf181d09 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 27 Jun 2026 03:07:51 +0000 Subject: [PATCH] fix: surface Workbench navigation root cause --- .../hwlab-node-web-observe-analyzer-source.ts | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/scripts/src/hwlab-node-web-observe-analyzer-source.ts b/scripts/src/hwlab-node-web-observe-analyzer-source.ts index 841ea0f6..805018fe 100644 --- a/scripts/src/hwlab-node-web-observe-analyzer-source.ts +++ b/scripts/src/hwlab-node-web-observe-analyzer-source.ts @@ -1400,6 +1400,115 @@ function buildSessionInvariantFindings(control, manifest = {}) { return findings; } +function buildControlledNavigationRootCauseFindings(control, manifest = {}) { + const commands = []; + for (const row of control || []) { + if (row?.phase !== "completed") continue; + if (row?.type !== "refreshCurrentSession" && row?.type !== "switchAwayAndBack") continue; + const detail = objectValue(row.detail); + const navigation = objectValue(detail.navigation); + const readiness = objectValue(navigation.readiness); + const snapshot = objectValue(readiness.snapshot); + const pageProvenance = objectValue(navigation.pageProvenance); + const blankShell = snapshot.workbenchShellVisible === false + && snapshot.sessionRailPresent === false + && snapshot.commandInputPresent === false + && snapshot.bodyTextHash === "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + const shellOrComposerMissing = snapshot.workbenchShellVisible === false + || snapshot.sessionRailPresent === false + || snapshot.commandInputPresent === false; + const degraded = navigation.degraded === true + || readiness.ok === false + || detail.routeOk === false + || blankShell + || (detail.activeOk === false && shellOrComposerMissing) + || (detail.composerReady === false && snapshot.commandInputPresent === false); + if (!degraded) continue; + const rootCause = stringOrNull(navigation.degradedReason) + ?? (readiness.ok === false ? stringOrNull(readiness.reason) : null) + ?? (detail.routeOk === false ? "route-session-not-hydrated" : null) + ?? (blankShell ? "workbench-blank-shell-after-navigation" : null) + ?? (detail.activeOk === false && shellOrComposerMissing ? "active-session-not-hydrated" : null) + ?? (detail.composerReady === false && snapshot.commandInputPresent === false ? "composer-not-ready" : null) + ?? "controlled-navigation-degraded"; + commands.push({ + commandId: stringOrNull(row.commandId), + type: stringOrNull(row.type), + commandTs: stringOrNull(row.ts), + afterRound: numberOrNull(detail.afterRound ?? row.input?.afterRound), + rootCause, + blocking: true, + canarySessionId: stringOrNull(detail.canarySessionId), + routeSessionId: stringOrNull(detail.routeSessionId), + activeSessionId: stringOrNull(detail.activeSessionId), + routeOk: detail.routeOk === true, + activeOk: detail.activeOk === true, + composerReady: detail.composerReady === true, + navigation: { + httpStatus: numberOrNull(navigation.httpStatus), + degraded: navigation.degraded === true, + degradedReason: stringOrNull(navigation.degradedReason), + beforePath: urlPath(navigation.beforeUrl), + afterPath: urlPath(navigation.afterUrl), + valuesRedacted: true, + }, + readiness: { + ok: readiness.ok === true, + reason: stringOrNull(readiness.reason), + durationMs: numberOrNull(readiness.durationMs), + path: stringOrNull(snapshot.path), + readyState: stringOrNull(snapshot.readyState), + workbenchShellVisible: snapshot.workbenchShellVisible === true, + sessionCreatePresent: snapshot.sessionCreatePresent === true, + sessionRailPresent: snapshot.sessionRailPresent === true, + commandInputPresent: snapshot.commandInputPresent === true, + activeTabPresent: snapshot.activeTabPresent === true, + loginVisible: snapshot.loginVisible === true, + blankShell, + bodyTextHash: stringOrNull(snapshot.bodyTextHash), + valuesRedacted: true, + }, + pageProvenance: { + pageLoadSeq: numberOrNull(pageProvenance.pageLoadSeq), + reason: stringOrNull(pageProvenance.reason), + observedAt: stringOrNull(pageProvenance.observedAt), + urlPath: stringOrNull(pageProvenance.urlPath), + documentReadyState: stringOrNull(pageProvenance.documentReadyState), + timeOrigin: numberOrNull(pageProvenance.timeOrigin), + httpStatus: numberOrNull(pageProvenance.httpStatus), + assetFingerprint: stringOrNull(pageProvenance.assetFingerprint), + scriptCount: numberOrNull(pageProvenance.scriptCount), + stylesheetCount: numberOrNull(pageProvenance.stylesheetCount), + scripts: arrayStrings(pageProvenance.scripts).slice(0, 8), + stylesheets: arrayStrings(pageProvenance.stylesheets).slice(0, 8), + valuesRedacted: true, + }, + observer: { + ok: detail.observer?.ok === true, + pageRole: stringOrNull(detail.observer?.pageRole), + pageId: stringOrNull(detail.observer?.pageId), + changed: detail.observer?.changed === true, + valuesRedacted: true, + }, + observerId: stringOrNull(manifest.jobId), + stateDir: stringOrNull(manifest.stateDir), + valuesRedacted: true, + }); + } + if (commands.length === 0) return []; + return [{ + id: "workbench-controlled-navigation-degraded-root-cause", + severity: "red", + summary: "controlled Workbench refresh/switch completed degraded; route may be correct but app shell, active session, or composer was not ready, so later Code Agent turns cannot continue", + count: commands.length, + blocking: true, + rootCauses: Array.from(new Set(commands.map((item) => item.rootCause))).slice(0, 12), + commands: commands.slice(0, 20), + next: "Investigate the first degraded command, then correlate browser requestfailed/static asset failures and Workbench hydration state before changing Code Agent/provider logic.", + valuesRedacted: true, + }]; +} + function sessionInvariantNavigationWindows(control) { const started = new Map(); const windows = []; @@ -1941,6 +2050,7 @@ function buildFindings(samples, control, network, errors, sampleMetrics, promptN const findings = []; const effectiveApiDomLag = apiDomLag || buildApiDomLagReport(samples, network); if (commandFailures.length > 0) findings.push({ id: "observer-command-failed", severity: "red", summary: "observer control commands failed; analyze must surface command failure instead of hiding it in command artifacts", count: commandFailures.length, commands: commandFailures.slice(0, 20) }); + findings.push(...buildControlledNavigationRootCauseFindings(control, manifest)); findings.push(...buildSessionInvariantFindings(control, manifest)); const commandTimes = control .filter((item) => item.phase === "completed" || item.phase === "started" || item.type === "observer-periodic-refresh")