fix: add code queue pr preflight

This commit is contained in:
Codex
2026-05-20 20:48:10 +00:00
parent e8b3c3ef32
commit 6c51512d64
10 changed files with 344 additions and 9 deletions
+192 -1
View File
@@ -147,6 +147,14 @@ interface CodexQueuesOptions {
limitExplicit: boolean;
}
interface CodexPrPreflightOptions {
remote: boolean;
pushDryRun: boolean;
pushDryRunRef: string | undefined;
issueNumber: number | null;
full: boolean;
}
type CodexRequestInit = { method?: string; body?: unknown };
type CodexResponseFetcher = (path: string, init?: CodexRequestInit) => unknown;
type AsyncCodexResponseFetcher = (path: string, init?: CodexRequestInit) => Promise<unknown>;
@@ -994,6 +1002,20 @@ function parseQueuesOptions(args: string[]): CodexQueuesOptions {
};
}
function parsePrPreflightOptions(args: string[]): CodexPrPreflightOptions {
assertKnownOptions(args, {
flags: ["--remote", "--push-dry-run", "--pushDryRun", "--full", "--raw"],
valueOptions: ["--push-dry-run-ref", "--pushDryRunRef", "--issue", "--issue-number", "--issueNumber"],
}, "codex pr-preflight");
return {
remote: hasFlag(args, "--remote"),
pushDryRun: hasFlag(args, "--push-dry-run") || hasFlag(args, "--pushDryRun"),
pushDryRunRef: optionValue(args, ["--push-dry-run-ref", "--pushDryRunRef"]),
issueNumber: nullablePositiveNumberOption(args, ["--issue", "--issue-number", "--issueNumber"]),
full: hasFlag(args, "--full") || hasFlag(args, "--raw"),
};
}
function parseJudgeOptions(args: string[]): CodexJudgeOptions {
assertKnownOptions(args, {
flags: ["--dry-run", "--no-call", "--include-prompt"],
@@ -1959,6 +1981,174 @@ function codeQueueDevReady(): unknown {
};
}
function compactCommandProbe(value: unknown): Record<string, unknown> | null {
const probe = asRecord(value);
if (probe === null) return null;
return {
command: probe.command ?? null,
args: Array.isArray(probe.args) ? probe.args : [],
ok: probe.ok ?? false,
exitCode: probe.exitCode ?? null,
signal: probe.signal ?? null,
error: probe.error ?? null,
stdout: typeof probe.stdout === "string" ? textView(probe.stdout, false, 1200) : null,
stderr: typeof probe.stderr === "string" ? textView(probe.stderr, false, 1200) : null,
};
}
function compactToolStatus(value: unknown): Record<string, unknown> {
const tool = asRecord(value) ?? {};
return {
ok: tool.ok ?? false,
path: tool.path ?? null,
version: tool.version ?? null,
};
}
function compactAgentPortStatus(value: unknown): Record<string, unknown> {
const port = asRecord(value) ?? {};
return {
ok: port.ok ?? false,
commandPath: port.commandPath ?? null,
version: port.version ?? null,
errors: Array.isArray(port.errors) ? port.errors : [],
};
}
function tokenCoverageStatus(credentials: Record<string, unknown>): Record<string, unknown> {
const ghTokenPresent = credentials.ghTokenPresent === true;
const githubTokenPresent = credentials.githubTokenPresent === true;
const anyEnvToken = ghTokenPresent || githubTokenPresent;
const anyGhCredentialStore = credentials.ghHostsConfigPresent === true || credentials.gitCredentialsPresent === true;
return {
ok: anyEnvToken,
source: ghTokenPresent ? "GH_TOKEN" : githubTokenPresent ? "GITHUB_TOKEN" : null,
ghTokenPresent,
githubTokenPresent,
ghCredentialStorePresent: anyGhCredentialStore,
runnerDisposition: anyEnvToken ? "ready" : "infra-blocked",
missing: anyEnvToken ? [] : ["GH_TOKEN", "GITHUB_TOKEN"],
scope: "scheduler-runner-env",
note: anyEnvToken
? "scheduler has a GitHub env token that can be forwarded to provider dev containers through CODE_QUEUE_REMOTE_CODEX_ENV_KEYS"
: "scheduler is missing GH_TOKEN/GITHUB_TOKEN; provider dev containers cannot receive a GitHub token even though CODE_QUEUE_REMOTE_CODEX_ENV_KEYS includes those keys",
};
}
function compactPrRuntimePreflight(preflight: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
const pull = asRecord(preflight.pullRequestDelivery) ?? {};
const tools = asRecord(pull.tools) ?? {};
const credentials = asRecord(pull.credentials) ?? {};
const git = asRecord(pull.git) ?? {};
const githubContext = asRecord(pull.githubContext) ?? {};
const egress = asRecord(pull.egress) ?? {};
const proxy = asRecord(egress.proxy) ?? {};
const remote = asRecord(pull.remote);
const ports = asRecord(preflight.ports) ?? {};
const tokenCoverage = tokenCoverageStatus(credentials);
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 result: Record<string, unknown> = {
ok,
checkedAt: preflight.checkedAt ?? pull.checkedAt ?? null,
runner: {
serviceId: "code-queue",
plane: "D601 k3s scheduler/runner",
queueScope: "all queues executed by the scheduler, including default",
cwd: preflight.cwd ?? null,
pid: preflight.pid ?? null,
},
tokenCoverage,
tools: {
git: compactToolStatus(tools.git),
gh: compactToolStatus(tools.gh),
hub: compactToolStatus(tools.hub),
jq: compactToolStatus(tools.jq),
ssh: compactToolStatus(tools.ssh),
curl: compactToolStatus(tools.curl),
},
agentPorts: {
codex: compactAgentPortStatus(ports.codex),
opencode: compactAgentPortStatus(ports.opencode),
},
git: {
insideWorktree: git.insideWorktree ?? false,
branch: git.branch ?? null,
head: git.head ?? null,
originMaster: git.originMaster ?? null,
remoteOrigin: git.remoteOrigin ?? null,
home: git.home ?? null,
homeWritable: git.homeWritable ?? false,
knownHostsPresent: git.knownHostsPresent ?? false,
privateKeyPresent: git.privateKeyPresent ?? false,
},
githubContext: {
host: githubContext.host ?? null,
apiBaseUrl: githubContext.apiBaseUrl ?? null,
repo: githubContext.repo ?? null,
issueProbeNumber: githubContext.issueProbeNumber ?? null,
},
egress: {
proxy: {
selectedProxyHost: proxy.selectedProxyHost ?? null,
selectedProxyPort: proxy.selectedProxyPort ?? null,
selectedProxyHostResolvable: proxy.selectedProxyHostResolvable ?? null,
},
githubDefault: compactCommandProbe(egress.githubDefault),
apiDefault: compactCommandProbe(egress.apiDefault),
issueApi: compactCommandProbe(egress.issueApi),
},
remote: remote === null ? null : {
gitLsRemote: compactCommandProbe(remote.gitLsRemote),
gitHttpsLsRemote: compactCommandProbe(remote.gitHttpsLsRemote),
githubSshAuthenticated: remote.githubSshAuthenticated ?? false,
ghAuthStatus: compactCommandProbe(remote.ghAuthStatus),
ghRepoView: compactCommandProbe(remote.ghRepoView),
ghIssueView: compactCommandProbe(remote.ghIssueView),
ghPrReadOnly: compactCommandProbe(remote.ghPrReadOnly),
},
pushDryRun: compactCommandProbe(pull.pushDryRun),
limitations,
risks,
runnerDisposition: ok ? "ready" : "infra-blocked",
recoveryHint: ok
? "Runner PR workflow has env-token coverage for the scheduler."
: "Inject GH_TOKEN or GITHUB_TOKEN into the Code Queue scheduler runtime secret for the target queue, then rerun this preflight before creating a PR.",
commands: {
local: "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
runner: "bun scripts/cli.ts codex pr-preflight --remote",
runnerPushDryRun: "bun scripts/cli.ts codex pr-preflight --remote --push-dry-run --push-dry-run-ref refs/heads/probe/code-queue-pr-capability",
rawProxy: "bun scripts/cli.ts microservice proxy code-queue /api/runtime-preflight?remote=1 --raw",
},
};
if (options.full) result.rawRuntimePreflight = preflight;
return result;
}
function codeQueuePrPreflight(optionArgs: string[] = [], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
const options = parsePrPreflightOptions(optionArgs);
const path = codeQueueProxyPath(`/api/runtime-preflight${queryString({
remote: options.remote ? 1 : undefined,
pushDryRun: options.pushDryRun ? 1 : undefined,
pushDryRunRef: options.pushDryRunRef,
issue: options.issueNumber,
})}`);
const response = unwrapCodexResponse(fetcher(path));
const preflight = asRecord(response.body.runtimePreflight);
if (preflight === null) throw new Error("Code Queue runtime-preflight response did not include runtimePreflight");
const compact = compactPrRuntimePreflight(preflight, options);
return {
ok: compact.ok,
upstream: response.upstream,
preflight: compact,
};
}
export function codexPrPreflightQueryForTest(optionArgs: string[], fetcher: CodexResponseFetcher): unknown {
return codeQueuePrPreflight(optionArgs, fetcher);
}
function codexSubmitTask(args: string[]): unknown {
const options = parseSubmitOptions(args);
const payload = submitPayload(options);
@@ -2069,6 +2259,7 @@ export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]
assertKnownOptions(args.slice(1), {}, `codex ${action}`);
return codeQueueDevReady();
}
if (action === "pr-preflight" || action === "runtime-preflight") return codeQueuePrPreflight(args.slice(1));
if (action === "output") {
const taskId = requireTaskId(taskIdArg, "codex output");
return codexOutputQuery(taskId, args.slice(2));
@@ -2103,5 +2294,5 @@ export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]
const taskId = requireTaskId(taskIdArg, "codex steer");
return codexSteerTask(taskId, args.slice(2));
}
throw new Error("codex command must be one of: submit, enqueue, task, summary, show, tasks, overview, output, judge, read, mark-read, dev-ready, health, queues, queue list, queue create, queue merge, move, steer, interrupt, cancel");
throw new Error("codex command must be one of: submit, enqueue, task, summary, show, tasks, overview, output, judge, read, mark-read, dev-ready, health, pr-preflight, runtime-preflight, queues, queue list, queue create, queue merge, move, steer, interrupt, cancel");
}