fix: classify transient GitHub connectivity
Merge PR #102 after rebasing onto current master and validating focused GitHub transient/Code Queue preflight behavior. GitHub DNS/API failures now classify as retryable github-transient with retry/backoff or keep-fresh-heartbeat-task guidance.
This commit is contained in:
@@ -320,7 +320,7 @@ interface CodexSkillsSyncOptions {
|
||||
full: boolean;
|
||||
}
|
||||
|
||||
type CodeQueuePrPreflightFailureKind = "auth-missing" | "runner-skills-blocker" | "proxy-gap" | "git-remote-gap" | "control-plane-missing" | "target-stack-not-running";
|
||||
type CodeQueuePrPreflightFailureKind = "auth-missing" | "runner-skills-blocker" | "github-transient" | "proxy-gap" | "git-remote-gap" | "control-plane-missing" | "target-stack-not-running";
|
||||
type CodeQueueObservationGapKind = "runner-local-observation-gap" | "control-plane-observation-gap" | null;
|
||||
|
||||
interface CodeQueuePrPreflightTransport {
|
||||
@@ -3817,6 +3817,64 @@ function compactCommandProbe(value: unknown): Record<string, unknown> | null {
|
||||
};
|
||||
}
|
||||
|
||||
function commandProbeFailureText(value: unknown): string {
|
||||
const probe = asRecord(value);
|
||||
if (probe === null || probe.ok === true) return "";
|
||||
const args = Array.isArray(probe.args) ? probe.args.map(String).join(" ") : "";
|
||||
return [
|
||||
typeof probe.command === "string" ? probe.command : "",
|
||||
args,
|
||||
typeof probe.error === "string" ? probe.error : "",
|
||||
typeof probe.stderr === "string" ? probe.stderr : "",
|
||||
typeof probe.stdout === "string" ? probe.stdout : "",
|
||||
].filter((item) => item.length > 0).join("\n");
|
||||
}
|
||||
|
||||
function commandProbeLooksLikeGithubTransient(value: unknown): boolean {
|
||||
const text = commandProbeFailureText(value);
|
||||
if (text.length === 0) return false;
|
||||
if (/\bproxy\b/iu.test(text) && /could not resolve|connect|tunnel|proxy/iu.test(text)) return false;
|
||||
return /temporary failure in name resolution|error connecting to api\.github\.com|getaddrinfo|could not resolve host:? (github\.com|api\.github\.com)|name or service not known|eai_again|enotfound|etimedout|econnreset|ehostunreach|enetunreach|failed to connect to (github\.com|api\.github\.com)|network is unreachable/iu.test(text);
|
||||
}
|
||||
|
||||
function githubTransientEvidence(pull: Record<string, unknown>): Record<string, unknown> | null {
|
||||
const egress = asRecord(pull.egress) ?? {};
|
||||
const remote = asRecord(pull.remote);
|
||||
const probes: Array<[string, unknown]> = [
|
||||
["egress.githubDefault", egress.githubDefault],
|
||||
["egress.apiDefault", egress.apiDefault],
|
||||
["egress.issueApi", egress.issueApi],
|
||||
];
|
||||
if (remote !== null) {
|
||||
probes.push(
|
||||
["remote.gitLsRemote", remote.gitLsRemote],
|
||||
["remote.gitHttpsLsRemote", remote.gitHttpsLsRemote],
|
||||
["remote.ghAuthStatus", remote.ghAuthStatus],
|
||||
["remote.ghRepoView", remote.ghRepoView],
|
||||
["remote.ghIssueView", remote.ghIssueView],
|
||||
["remote.ghPrReadOnly", remote.ghPrReadOnly],
|
||||
);
|
||||
}
|
||||
const failedProbes = probes
|
||||
.map(([name, probe]) => ({ name, probe, text: commandProbeFailureText(probe) }))
|
||||
.filter((item) => commandProbeLooksLikeGithubTransient(item.probe))
|
||||
.slice(0, 4);
|
||||
if (failedProbes.length === 0) return null;
|
||||
return {
|
||||
kind: "github-transient",
|
||||
retryable: true,
|
||||
scope: "github-dns-api",
|
||||
failedProbes: failedProbes.map((item) => ({
|
||||
name: item.name,
|
||||
preview: compactInlinePreview(item.text, 240),
|
||||
})),
|
||||
commanderAction: "retry/backoff; if Code Queue heartbeat or trace is fresh, keep the task running and continue supervision",
|
||||
notAuthMissing: true,
|
||||
notPrSemanticFailure: true,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function compactToolStatus(value: unknown): Record<string, unknown> {
|
||||
const tool = asRecord(value) ?? {};
|
||||
return {
|
||||
@@ -4183,21 +4241,26 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
|
||||
const limitations = Array.isArray(pull.limitations) ? pull.limitations.map(String) : [];
|
||||
const risks = Array.isArray(pull.risks) ? pull.risks.map(String) : [];
|
||||
const ok = preflight.ok === true && tokenCoverage.ok === true;
|
||||
const githubTransient = githubTransientEvidence(pull);
|
||||
const skillsBlocked = skills !== null && skills.ok !== true;
|
||||
const skillsSyncBlocked = skillsSync !== null && skillsSync.ok !== true;
|
||||
const failureKind = !tokenCoverage.ok
|
||||
? "auth-missing"
|
||||
: skillsBlocked || skillsSyncBlocked
|
||||
? "runner-skills-blocker"
|
||||
: limitations.some((item) => item.includes("git ls-remote") || item.includes("git push --dry-run failed"))
|
||||
? "git-remote-gap"
|
||||
: !preflight.ok
|
||||
? "proxy-gap"
|
||||
: null;
|
||||
: githubTransient !== null
|
||||
? "github-transient"
|
||||
: limitations.some((item) => item.includes("git ls-remote") || item.includes("git push --dry-run failed"))
|
||||
? "git-remote-gap"
|
||||
: !preflight.ok
|
||||
? "proxy-gap"
|
||||
: null;
|
||||
const degradedReason = failureKind === "auth-missing"
|
||||
? "auth-broker-needed"
|
||||
: failureKind === "runner-skills-blocker"
|
||||
? typeof skillsSync?.blocker === "string" ? skillsSync.blocker : typeof skills?.blocker === "string" ? skills.blocker : "runner-skills-degraded"
|
||||
: failureKind === "github-transient"
|
||||
? "github-dns-api-transient"
|
||||
: failureKind === "git-remote-gap"
|
||||
? "git remote probe failed"
|
||||
: failureKind === "proxy-gap"
|
||||
@@ -4326,12 +4389,19 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
|
||||
limitations,
|
||||
risks,
|
||||
runnerDisposition: ok ? "ready" : "infra-blocked",
|
||||
...(githubTransient === null ? {} : {
|
||||
retryable: true,
|
||||
commanderAction: "retry-backoff-or-keep-running-if-heartbeat-fresh",
|
||||
githubTransient,
|
||||
}),
|
||||
activeRunnerDevContainer,
|
||||
recoveryHint: ok
|
||||
? tokenCoverage.source === "auth-broker"
|
||||
? "Runner PR workflow has auth-broker coverage for GitHub REST preflight; real PR creation still requires commander authorization."
|
||||
: "Runner PR workflow has env-token coverage for the scheduler."
|
||||
: "Scheduler preflight lacks GitHub auth coverage for scheduler-scoped admission only. First verify the active task with repo-native gh auth status and PR create dry-run; long-term configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN into the scheduler runtime secret.",
|
||||
: githubTransient !== null
|
||||
? "GitHub DNS/API connectivity failed transiently. Retry with backoff; if the task heartbeat or trace is fresh, keep supervising the running task instead of closing or requeueing business work."
|
||||
: "Scheduler preflight lacks GitHub auth coverage for scheduler-scoped admission only. First verify the active task with repo-native gh auth status and PR create dry-run; long-term configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN into the scheduler runtime secret.",
|
||||
commands: {
|
||||
local: "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
|
||||
runner: "bun scripts/cli.ts codex pr-preflight --remote",
|
||||
@@ -4582,6 +4652,8 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
|
||||
runnerDisposition: compact.runnerDisposition,
|
||||
failureKind: compact.failureKind ?? null,
|
||||
degradedReason: compact.degradedReason ?? null,
|
||||
...(compact.retryable === true ? { retryable: true } : {}),
|
||||
...(typeof compact.commanderAction === "string" ? { commanderAction: compact.commanderAction } : {}),
|
||||
authScopeSummary: compact.authScopeSummary,
|
||||
scopeBoundary: compact.scopeBoundary,
|
||||
activeRunnerDevContainer: compact.activeRunnerDevContainer,
|
||||
@@ -4638,6 +4710,8 @@ export async function codexPrPreflightQueryAsync(optionArgs: string[], fetcher:
|
||||
runnerDisposition: compact.runnerDisposition,
|
||||
failureKind: compact.failureKind ?? null,
|
||||
degradedReason: compact.degradedReason ?? null,
|
||||
...(compact.retryable === true ? { retryable: true } : {}),
|
||||
...(typeof compact.commanderAction === "string" ? { commanderAction: compact.commanderAction } : {}),
|
||||
authScopeSummary: compact.authScopeSummary,
|
||||
scopeBoundary: compact.scopeBoundary,
|
||||
activeRunnerDevContainer: compact.activeRunnerDevContainer,
|
||||
|
||||
Reference in New Issue
Block a user