From c0062455fd06a10f8b0bf07e92c84c3a0ca673e9 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Jul 2026 18:14:37 +0000 Subject: [PATCH] fix: bound follower debug json output --- .../references/branch-follower.md | 2 + scripts/src/cicd-debug.ts | 55 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/.agents/skills/unidesk-cicd/references/branch-follower.md b/.agents/skills/unidesk-cicd/references/branch-follower.md index 4f702120..489d7fd6 100644 --- a/.agents/skills/unidesk-cicd/references/branch-follower.md +++ b/.agents/skills/unidesk-cicd/references/branch-follower.md @@ -34,6 +34,8 @@ When a repeated runtime pitfall or visibility defect is found during branch-foll `debug-step` wrappers must be failure-visible and non-crashing. If the target-side Job fails, returns an older schema, or omits optional summary fields, the operator-facing CLI must render `-`/null plus the target error and Job identity; it must not throw a local TypeError before showing the target evidence. +`debug-step` output must stay bounded in both text and JSON modes. The default machine payload should include step result, compact state/status/decision/write summaries, target Job identity and short error/timing fields only. Full target Job logs, full target JSON and long stdout/stderr tails belong behind explicit drill-down, not in the default `--json` payload. + ## Source Authority - Follower decisions must not read host source worktrees, target dev directories, `.worktree/*`, local git state, or direct GitHub branch refs. diff --git a/scripts/src/cicd-debug.ts b/scripts/src/cicd-debug.ts index a89015cd..773185e5 100644 --- a/scripts/src/cicd-debug.ts +++ b/scripts/src/cicd-debug.ts @@ -157,16 +157,65 @@ function runTargetDebugStepJob(registry: BranchFollowerRegistry, options: Parsed exitCode: result.exitCode, timedOut: result.timedOut, parsed: parsed !== null, - stdoutTail: redactText(tailText(result.stdout, options.full ? 4000 : 1000)), - stderrTail: redactText(tailText(result.stderr, options.full ? 2000 : 800)), + stdoutTail: redactText(tailText(result.stdout, 1000)), + stderrTail: redactText(tailText(result.stderr, 800)), }, - targetResult: parsed, + targetResult: compactTargetDebugResult(parsed), stateAfter: asOptionalRecord(parsed?.stateAfter) ?? stateSnapshot(state, followerId), parsedDownstreamCliOutput: false, next: debugNext(followerId), }; } +function compactTargetDebugResult(parsed: Record | null): Record | null { + if (parsed === null) return null; + const stateBefore = asOptionalRecord(parsed.stateBefore); + const status = asOptionalRecord(parsed.status); + const decision = asOptionalRecord(parsed.decision); + const stateWrite = asOptionalRecord(parsed.stateWrite); + const stateAfter = asOptionalRecord(parsed.stateAfter); + return { + ok: parsed.ok === true, + action: stringOrNull(parsed.action), + step: stringOrNull(parsed.step), + follower: stringOrNull(parsed.follower), + stateBefore: compactStateLike(stateBefore), + status: status === null ? null : { + ok: status.ok === true, + phase: stringOrNull(status.phase), + observedSha: stringOrNull(status.observedSha), + targetSha: stringOrNull(status.targetSha), + aligned: status.aligned === true ? true : status.aligned === false ? false : null, + pipelineRun: stringOrNull(status.pipelineRun), + message: stringOrNull(status.message), + }, + decision: decision === null ? null : { + phase: stringOrNull(decision.phase), + observedSha: stringOrNull(decision.observedSha), + targetSha: stringOrNull(decision.targetSha), + decision: stringOrNull(decision.decision), + totalSeconds: numberOrNull(asOptionalRecord(decision.timings)?.totalSeconds), + totalStatus: stringOrNull(asOptionalRecord(decision.timings)?.totalStatus), + }, + stateWrite, + stateAfter: compactStateLike(stateAfter), + }; +} + +function compactStateLike(value: Record | null): Record | null { + if (value === null) return null; + const metadata = asOptionalRecord(value.metadata); + return { + ok: value.ok === true, + phase: stringOrNull(value.phase), + observedSha: stringOrNull(value.observedSha), + targetSha: stringOrNull(value.targetSha), + totalSeconds: numberOrNull(value.totalSeconds), + timingStatus: stringOrNull(value.timingStatus), + resourceVersion: stringOrNull(metadata?.resourceVersion), + }; +} + function debugDecisionOptions(options: ParsedOptions): ParsedOptions { return { ...options, confirm: false, dryRun: true, wait: false, recordState: false }; }