fix: bound follower debug json output

This commit is contained in:
Codex
2026-07-03 18:14:37 +00:00
parent 530e02a243
commit c0062455fd
2 changed files with 54 additions and 3 deletions
@@ -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.
+52 -3
View File
@@ -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<string, unknown> | null): Record<string, unknown> | 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<string, unknown> | null): Record<string, unknown> | 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 };
}