fix: compact codex pr preflight output

This commit is contained in:
Codex
2026-05-23 01:06:09 +00:00
parent 6f40350a62
commit d4c6fadc49
5 changed files with 329 additions and 72 deletions
@@ -347,10 +347,11 @@ async function main(): Promise<void> {
assertCondition(asRecord(fallback.disclosure).fullObservationsOmitted === true, "healthy remote fallback should disclose omitted full observations", fallback.disclosure);
assertCondition(asRecord(fallback.localObservationSummary).failureKind === "target-stack-not-running", "healthy remote fallback should keep bounded local observation summary", fallback.localObservationSummary);
assertCondition(asRecord(fallback.remoteObservationSummary).ok === true, "healthy remote fallback should keep bounded remote observation summary", fallback.remoteObservationSummary);
const fallbackPreflight = asRecord(fallback.preflight);
assertCondition(fallbackPreflight.ok === true, "remote fallback preflight should stay ready", fallbackPreflight);
assertCondition(asRecord(fallbackPreflight.tokenCoverage).source === "GH_TOKEN", "token source should be GH_TOKEN", fallbackPreflight.tokenCoverage);
assertCondition(asRecord(fallbackPreflight.prCapabilityContract).targetBranch === "master", "target branch should stay master", fallbackPreflight.prCapabilityContract);
assertCondition(fallback.preflight === undefined, "remote fallback default output should omit detailed preflight", fallback);
const fallbackSchedulerPreflight = asRecord(fallback.schedulerPreflight);
assertCondition(fallbackSchedulerPreflight.authReady === true, "remote fallback scheduler summary should stay ready", fallbackSchedulerPreflight);
assertCondition(fallbackSchedulerPreflight.authSource === "GH_TOKEN", "token source should be GH_TOKEN", fallbackSchedulerPreflight);
assertCondition(asRecord(fallback.prCapability).targetBranch === "master", "target branch should stay master", fallback.prCapability);
const authMissing = await codexPrPreflightQueryForTest(["--remote"], {
config: null,
@@ -406,23 +407,26 @@ async function main(): Promise<void> {
assertCondition(directAuthObservationGap.kind === "runner-local-observation-gap", "auth missing after remote fallback should keep local backend-core absence scoped as runner-local observation gap", directAuthObservationGap);
const directAuthSummary = asRecord(directAuthMissingRecord.authScopeSummary);
const directAuthScopeBoundary = asRecord(directAuthMissingRecord.scopeBoundary);
const directAuthActiveRunner = asRecord(directAuthMissingRecord.activeRunnerDevContainer);
const directAuthActiveRunner = asRecord(directAuthMissingRecord.activeRunnerPrCapability);
const directAuthRecommendedActions = Array.isArray(directAuthMissingRecord.recommendedActions) ? directAuthMissingRecord.recommendedActions : [];
assertCondition(directAuthSummary.schedulerAuthMissingIsScoped === true, "remote auth-missing should lead with scheduler-scoped auth summary", directAuthSummary);
assertCondition(String(directAuthSummary.interpretation ?? "").includes("does not prove"), "remote auth summary must not imply active runner PR incapability", directAuthSummary);
assertCondition(directAuthScopeBoundary.scopesAreIndependent === true, "remote auth-missing must distinguish scheduler env from active runner dev container", directAuthScopeBoundary);
assertCondition(directAuthScopeBoundary.schedulerAuthMissingDoesNotMeanActiveRunnerCannotCreatePr === true, "remote auth-missing should expose the explicit PR capability boundary", directAuthScopeBoundary);
assertCondition(String(directAuthScopeBoundary.authMissingInterpretation ?? "").includes("do not simplify"), "remote auth-missing must warn against overbroad interpretation", directAuthScopeBoundary);
assertCondition(directAuthActiveRunner.notEquivalentToSchedulerEnv === true, "active runner token capability must be a separate scope", directAuthActiveRunner);
assertCondition(directAuthActiveRunner.independentOfSchedulerPreflight === true, "active runner token capability must be a separate scope", directAuthActiveRunner);
assertCondition(Array.isArray(directAuthMissingRecord.recommendedActions), "remote auth-missing should expose bounded recommended actions", directAuthMissingRecord.recommendedActions);
assertCondition(directAuthRecommendedActions.length === 3, "remote auth-missing recommended actions should stay bounded", directAuthRecommendedActions);
assertCondition(directAuthRecommendedActions.some((action) => asRecord(action).action === "verify-current-runner-auth"), "remote auth-missing should recommend active runner auth status first", directAuthRecommendedActions);
assertCondition(directAuthRecommendedActions.some((action) => String(asRecord(action).command ?? "").includes("gh pr create") && asRecord(action).writesRemote === false), "remote auth-missing should recommend PR create dry-run", directAuthRecommendedActions);
assertCondition(directAuthMissingRecord.localObservation === undefined, "auth-missing remote fallback default output should omit full local observation", directAuthMissingRecord);
assertCondition(directAuthMissingRecord.remoteObservation === undefined, "auth-missing remote fallback default output should omit full remote observation", directAuthMissingRecord);
assertCondition(directAuthMissingRecord.preflight === undefined, "auth-missing remote fallback default output should omit detailed preflight", directAuthMissingRecord);
assertCondition(asRecord(directAuthMissingRecord.disclosure).fullObservationsOmitted === true, "auth-missing remote fallback should disclose omitted full observations", directAuthMissingRecord.disclosure);
assertCondition(asRecord(directAuthMissingRecord.disclosure).fullDetailOmitted === true, "auth-missing remote fallback should disclose omitted full detail", directAuthMissingRecord.disclosure);
assertCondition(asRecord(directAuthMissingRecord.localObservationSummary).failureKind === "target-stack-not-running", "auth-missing remote fallback should keep bounded local observation summary", directAuthMissingRecord.localObservationSummary);
assertCondition(asRecord(asRecord(directAuthMissingRecord.remoteObservationSummary).tokenCoverage).scope === "scheduler-runner-env", "auth-missing remote fallback should keep bounded remote token scope", directAuthMissingRecord.remoteObservationSummary);
assertCondition(JSON.stringify(directAuthMissingRecord).length < 12000, "auth-missing remote fallback default output should stay compact", { chars: JSON.stringify(directAuthMissingRecord).length });
const directAuthMissingFull = await codexPrPreflightQueryForTest(["--remote", "--full"], {
config: { network: { publicHost: "74.48.78.17", frontend: { port: 18081 } } } as unknown as UniDeskConfig,
@@ -599,15 +603,63 @@ async function main(): Promise<void> {
assertCondition(githubTransientRecord.runnerDisposition === "infra-blocked", "GitHub transient keeps infra-blocked disposition for legacy callers", githubTransientRecord);
assertCondition(githubTransientRecord.retryable === true, "GitHub transient should expose top-level retryable=true", githubTransientRecord);
assertCondition(githubTransientRecord.commanderAction === "retry-backoff-or-keep-running-if-heartbeat-fresh", "GitHub transient should expose top-level commander action", githubTransientRecord);
const githubTransientPreflight = asRecord(githubTransientRecord.preflight);
assertCondition(githubTransientPreflight.retryable === true, "GitHub transient preflight should be retryable", githubTransientPreflight);
assertCondition(githubTransientPreflight.commanderAction === "retry-backoff-or-keep-running-if-heartbeat-fresh", "GitHub transient should expose bounded commander action", githubTransientPreflight);
const githubTransient = asRecord(githubTransientPreflight.githubTransient);
assertCondition(githubTransientRecord.preflight === undefined, "GitHub transient default output should omit detailed preflight", githubTransientRecord);
const githubTransient = asRecord(githubTransientRecord.githubTransient);
assertCondition(githubTransient.kind === "github-transient", "GitHub transient evidence should identify kind", githubTransient);
assertCondition(githubTransient.notAuthMissing === true, "GitHub transient must not be auth-missing", githubTransient);
assertCondition(githubTransient.notPrSemanticFailure === true, "GitHub transient must not be PR semantic failure", githubTransient);
assertCondition(Array.isArray(githubTransient.failedProbes) && githubTransient.failedProbes.length <= 4, "GitHub transient evidence should stay bounded", githubTransient);
assertCondition(String(githubTransient.commanderAction ?? "").includes("keep the task running"), "GitHub transient action should preserve fresh-heartbeat tasks", githubTransient);
const githubTransientFullRecord = asRecord(await codexPrPreflightQueryForTest(["--remote", "--full"], {
config: null,
coreFetch: () => ({
ok: true,
status: 200,
body: {
runtimePreflight: rawRuntimePreflightFixture({
ok: false,
pullRequestDelivery: {
...asRecord(rawRuntimePreflightFixture().pullRequestDelivery),
ok: false,
credentials: {
ghTokenPresent: true,
githubTokenPresent: false,
ghHostsConfigPresent: false,
gitCredentialsPresent: false,
},
egress: {
proxy: {
selectedProxyHost: "d601-provider-egress-proxy.unidesk.svc.cluster.local",
selectedProxyPort: "18789",
selectedProxyHostResolvable: true,
},
githubDefault: { command: "curl", args: ["-IsS", "https://github.com"], ok: false, exitCode: 6, signal: null, error: null, stdout: "", stderr: "curl: (6) Could not resolve host: github.com" },
apiDefault: { command: "curl", args: ["-IsS", "https://api.github.com"], ok: false, exitCode: 6, signal: null, error: null, stdout: "", stderr: "curl: (6) Could not resolve host: api.github.com" },
issueApi: null,
},
remote: {
gitLsRemote: { command: "git", args: ["ls-remote", "--heads", "origin", "master"], ok: false, exitCode: 128, signal: null, error: null, stdout: "", stderr: "ssh: Could not resolve hostname github.com: Temporary failure in name resolution" },
gitHttpsLsRemote: null,
githubSshAuthenticated: false,
ghAuthStatus: { command: "gh", args: ["auth", "status"], ok: false, exitCode: 1, signal: null, error: null, stdout: "", stderr: "error connecting to api.github.com" },
ghRepoView: null,
ghIssueView: null,
ghPrReadOnly: null,
},
limitations: [
"GitHub HTTPS probe failed with the default environment/proxy",
"GitHub API probe failed with the default environment/proxy",
"git ls-remote origin master failed",
],
risks: [],
},
}),
},
}),
}), "github transient full record");
const githubTransientPreflight = asRecord(githubTransientFullRecord.preflight);
assertCondition(githubTransientPreflight.retryable === true, "GitHub transient full preflight should be retryable", githubTransientPreflight);
assertCondition(githubTransientPreflight.commanderAction === "retry-backoff-or-keep-running-if-heartbeat-fresh", "GitHub transient full preflight should expose bounded commander action", githubTransientPreflight);
let observedDryRunPath = "";
const dryRunContract = await codexPrPreflightQueryForTest([
@@ -707,24 +759,23 @@ async function main(): Promise<void> {
assertCondition(observedDryRunPath === "/api/microservices/code-queue/proxy/api/runtime-preflight?remote=1&pushDryRun=1&pushDryRunRef=refs%2Fheads%2Fprobe%2Fcode-queue-pr-capability&prCreateDryRun=1&prCreateDryRunHead=code-queue%2Fissue-35-pr-dry-run-probe&issue=20", "combined dry-run query should pass all requested guards", { observedDryRunPath });
const dryRunRecord = asRecord(dryRunContract);
assertCondition(dryRunRecord.failureKind === "auth-missing", "missing runner token should remain auth-missing even when system gh is absent", dryRunRecord);
const dryRunPreflight = asRecord(dryRunRecord.preflight);
const dryRunAuthBroker = asRecord(dryRunPreflight.authBroker);
const dryRunScopeBoundary = asRecord(dryRunPreflight.scopeBoundary);
const dryRunActiveRunner = asRecord(dryRunPreflight.activeRunnerDevContainer);
assertCondition(dryRunRecord.preflight === undefined, "combined dry-run default output should omit detailed preflight", dryRunRecord);
const dryRunSchedulerPreflight = asRecord(dryRunRecord.schedulerPreflight);
const dryRunAuthBroker = asRecord(dryRunSchedulerPreflight.authBroker);
const dryRunScopeBoundary = asRecord(dryRunRecord.scopeBoundary);
const dryRunActiveRunner = asRecord(dryRunRecord.activeRunnerPrCapability);
assertCondition(dryRunAuthBroker.source === "broker/auth-broker-needed", "missing runner token should expose broker/auth-broker-needed", dryRunAuthBroker);
assertCondition(dryRunAuthBroker.degradedReason === "auth-broker-needed", "auth broker degraded reason should be explicit", dryRunAuthBroker);
assertCondition(dryRunSchedulerPreflight.degradedReason === "auth-broker-needed", "auth broker degraded reason should be explicit", dryRunSchedulerPreflight);
assertCondition(dryRunScopeBoundary.scopesAreIndependent === true, "local compact preflight should expose independent auth scopes", dryRunScopeBoundary);
assertCondition(dryRunActiveRunner.scope === "current-cli-process", "local compact preflight should expose current CLI process capability", dryRunActiveRunner);
const dryRunBrokerEvidence = asRecord(dryRunAuthBroker.evidence);
assertCondition(dryRunBrokerEvidence.systemGhBinaryOk === false, "system gh absence should be reported separately", dryRunBrokerEvidence);
assertCondition(dryRunBrokerEvidence.unideskGhCliOk === true, "UniDesk REST gh CLI should not be marked unavailable because system gh is missing", dryRunBrokerEvidence);
assertCondition(dryRunBrokerEvidence.systemGhMissingMisclassifiedAsUniDeskCliMissing === false, "system gh absence must not be misclassified", dryRunBrokerEvidence);
const dryRunPrContract = asRecord(dryRunPreflight.prCapabilityContract);
assertCondition(dryRunActiveRunner.scope === "current-cli-process", "compact default should expose current CLI process capability", dryRunActiveRunner);
const dryRunPrContract = asRecord(dryRunRecord.prCapability);
assertCondition(dryRunPrContract.systemGhBinaryRequiredForWrites === false, "system gh absence must not be required for UniDesk REST gh writes", dryRunPrContract);
assertCondition(dryRunPrContract.unideskGhCliOk === true, "UniDesk REST gh CLI should not be marked unavailable because system gh is missing", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.pushDryRun).requested === true, "push dry-run should be requested", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.pushDryRun).writesRemote === false, "push dry-run must be marked non-writing", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).requested === true, "PR create dry-run should be requested", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).writesRemote === false, "PR create dry-run must be marked non-writing", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).headBranch === "code-queue/issue-35-pr-dry-run-probe", "PR dry-run head should come from the option", dryRunPrContract);
assertCondition(dryRunPrContract.sourceBranch === "code-queue/issue-35-pr-dry-run-probe", "PR dry-run head should come from the option", dryRunPrContract);
const brokerIssuedContract = await codexPrPreflightQueryForTest(["--remote"], {
config: null,
@@ -768,18 +819,17 @@ async function main(): Promise<void> {
});
const brokerIssuedRecord = asRecord(brokerIssuedContract);
assertCondition(brokerIssuedRecord.ok === true, "broker-issued token branch should be ready", brokerIssuedRecord);
const brokerIssuedPreflight = asRecord(brokerIssuedRecord.preflight);
const brokerIssuedTokenCoverage = asRecord(brokerIssuedPreflight.tokenCoverage);
const brokerIssuedAuthBroker = asRecord(brokerIssuedPreflight.authBroker);
const brokerIssuedCapability = asRecord(brokerIssuedAuthBroker.capability);
const brokerIssuedPrContract = asRecord(brokerIssuedPreflight.prCapabilityContract);
assertCondition(brokerIssuedTokenCoverage.source === "auth-broker", "broker-issued branch should use auth-broker token coverage", brokerIssuedTokenCoverage);
assertCondition(brokerIssuedTokenCoverage.credentialSource === "broker-issued-token", "broker-issued branch should expose broker-issued-token capability", brokerIssuedTokenCoverage);
assertCondition(brokerIssuedRecord.preflight === undefined, "broker-issued default output should omit detailed preflight", brokerIssuedRecord);
const brokerIssuedScheduler = asRecord(brokerIssuedRecord.schedulerPreflight);
const brokerIssuedAuthBroker = asRecord(brokerIssuedScheduler.authBroker);
const brokerIssuedPrContract = asRecord(brokerIssuedRecord.prCapability);
assertCondition(brokerIssuedScheduler.authSource === "auth-broker", "broker-issued branch should use auth-broker token coverage", brokerIssuedScheduler);
assertCondition(brokerIssuedScheduler.credentialSource === "broker-issued-token", "broker-issued branch should expose broker-issued-token capability", brokerIssuedScheduler);
assertCondition(brokerIssuedAuthBroker.source === "auth-broker", "broker-issued branch should expose authBroker.source", brokerIssuedAuthBroker);
assertCondition(brokerIssuedAuthBroker.nextAction === "use-auth-broker", "broker-issued branch should expose nextAction", brokerIssuedAuthBroker);
assertCondition(brokerIssuedCapability.systemGhBinaryRequiredForWrites === false, "broker-issued branch should not require system gh binary", brokerIssuedCapability);
assertCondition(brokerIssuedCapability.realPrCreateRequiresCommanderAuthorization === true, "real PR creation should still require commander authorization", brokerIssuedCapability);
assertCondition(asRecord(brokerIssuedPrContract.authBroker).source === "auth-broker", "PR capability should include broker source", brokerIssuedPrContract);
assertCondition(brokerIssuedAuthBroker.capabilitySource === "broker-issued-token", "broker-issued branch should expose broker-issued capability", brokerIssuedAuthBroker);
assertCondition(brokerIssuedPrContract.systemGhBinaryRequiredForWrites === false, "broker-issued branch should not require system gh binary", brokerIssuedPrContract);
assertCondition(brokerIssuedPrContract.realPrCreateRequiresCommanderAuthorization === true, "real PR creation should still require commander authorization", brokerIssuedPrContract);
const envTokenContract = await codexPrPreflightQueryForTest(["--remote"], {
config: null,
@@ -829,11 +879,11 @@ async function main(): Promise<void> {
});
const envTokenRecord = asRecord(envTokenContract);
assertCondition(envTokenRecord.ok === true, "env token branch should be ready", envTokenRecord);
const envTokenPreflight = asRecord(envTokenRecord.preflight);
const envTokenCoverage = asRecord(envTokenPreflight.tokenCoverage);
const envTokenAuthBroker = asRecord(envTokenPreflight.authBroker);
assertCondition(envTokenCoverage.source === "GH_TOKEN", "env token branch should expose GH_TOKEN source", envTokenCoverage);
assertCondition(envTokenCoverage.credentialSource === "env-token", "env token branch should expose env-token capability", envTokenCoverage);
assertCondition(envTokenRecord.preflight === undefined, "env-token default output should omit detailed preflight", envTokenRecord);
const envTokenScheduler = asRecord(envTokenRecord.schedulerPreflight);
const envTokenAuthBroker = asRecord(envTokenScheduler.authBroker);
assertCondition(envTokenScheduler.authSource === "GH_TOKEN", "env token branch should expose GH_TOKEN source", envTokenScheduler);
assertCondition(envTokenScheduler.credentialSource === "env-token", "env token branch should expose env-token capability", envTokenScheduler);
assertCondition(envTokenAuthBroker.source === "GH_TOKEN", "env token branch should not pretend broker is configured", envTokenAuthBroker);
assertCondition(envTokenAuthBroker.nextAction === "use-env-token-until-auth-broker-live", "env token branch should still point at broker migration", envTokenAuthBroker);
@@ -852,25 +902,25 @@ async function main(): Promise<void> {
const missingTokenTopSummary = asRecord(missingTokenRecord.authScopeSummary);
const missingTokenTopScopeBoundary = asRecord(missingTokenRecord.scopeBoundary);
const missingTokenTopActions = Array.isArray(missingTokenRecord.recommendedActions) ? missingTokenRecord.recommendedActions : [];
const missingTokenPreflight = asRecord(missingTokenRecord.preflight);
const missingTokenAuthBroker = asRecord(missingTokenPreflight.authBroker);
const missingTokenSummary = asRecord(missingTokenPreflight.authScopeSummary);
const missingTokenScopeBoundary = asRecord(missingTokenPreflight.scopeBoundary);
const missingTokenActiveRunner = asRecord(missingTokenPreflight.activeRunnerDevContainer);
const missingTokenActions = Array.isArray(missingTokenPreflight.recommendedActions) ? missingTokenPreflight.recommendedActions : [];
const missingTokenCapability = asRecord(missingTokenAuthBroker.capability);
assertCondition(missingTokenRecord.preflight === undefined, "missing-token default output should omit detailed preflight", missingTokenRecord);
const missingTokenScheduler = asRecord(missingTokenRecord.schedulerPreflight);
const missingTokenAuthBroker = asRecord(missingTokenScheduler.authBroker);
const missingTokenScopeBoundary = asRecord(missingTokenRecord.scopeBoundary);
const missingTokenActiveRunner = asRecord(missingTokenRecord.activeRunnerPrCapability);
const missingTokenActions = Array.isArray(missingTokenRecord.recommendedActions) ? missingTokenRecord.recommendedActions : [];
const missingTokenPrCapability = asRecord(missingTokenRecord.prCapability);
assertCondition(missingTokenTopSummary.schedulerAuthMissingIsScoped === true, "missing-token top-level summary should expose scoped scheduler auth missing", missingTokenTopSummary);
assertCondition(missingTokenTopScopeBoundary.schedulerAuthMissingDoesNotMeanActiveRunnerCannotCreatePr === true, "missing-token top-level boundary should be prominent", missingTokenTopScopeBoundary);
assertCondition(Array.isArray(missingTokenRecord.recommendedActions) && missingTokenTopActions.length === 3, "missing-token top-level recommended actions should stay bounded", missingTokenRecord.recommendedActions);
assertCondition(missingTokenAuthBroker.source === "broker/auth-broker-needed", "missing-token branch should expose broker/auth-broker-needed", missingTokenAuthBroker);
assertCondition(missingTokenAuthBroker.nextAction === "configure-auth-broker-or-env-token", "missing-token branch should expose nextAction", missingTokenAuthBroker);
assertCondition(missingTokenCapability.source === "missing-token", "missing-token branch should expose missing-token capability", missingTokenCapability);
assertCondition(missingTokenCapability.systemGhBinaryRequiredForWrites === false, "missing-token branch should still not require system gh binary for UniDesk gh CLI", missingTokenCapability);
assertCondition(missingTokenSummary.schedulerAuthMissingIsScoped === true, "missing-token compact preflight should expose scoped scheduler auth missing", missingTokenSummary);
assertCondition(missingTokenAuthBroker.capabilitySource === "missing-token", "missing-token branch should expose missing-token capability", missingTokenAuthBroker);
assertCondition(missingTokenPrCapability.systemGhBinaryRequiredForWrites === false, "missing-token branch should still not require system gh binary for UniDesk gh CLI", missingTokenPrCapability);
assertCondition(missingTokenTopSummary.schedulerAuthMissingIsScoped === true, "missing-token compact summary should expose scoped scheduler auth missing", missingTokenTopSummary);
assertCondition(String(missingTokenScopeBoundary.currentRunnerCheck ?? "").includes("gh auth status"), "missing-token branch should point to active runner auth check", missingTokenScopeBoundary);
assertCondition(String(missingTokenScopeBoundary.currentRunnerCheck ?? "").includes("gh pr create --dry-run"), "missing-token branch should point to active runner PR create dry-run", missingTokenScopeBoundary);
assertCondition(missingTokenActiveRunner.relationToRemotePreflight === "independent-scope; scheduler-runner-env auth-missing does not prove the active runner/dev container lacks GitHub PR capability", "missing-token branch should not overstate active runner PR capability", missingTokenActiveRunner);
assertCondition(Array.isArray(missingTokenPreflight.recommendedActions) && missingTokenActions.length === 3, "missing-token compact recommended actions should stay bounded", missingTokenPreflight.recommendedActions);
assertCondition(missingTokenActiveRunner.independentOfSchedulerPreflight === true, "missing-token branch should not overstate active runner PR capability", missingTokenActiveRunner);
assertCondition(Array.isArray(missingTokenRecord.recommendedActions) && missingTokenActions.length === 3, "missing-token compact recommended actions should stay bounded", missingTokenRecord.recommendedActions);
assertCondition(missingTokenActions.some((action) => String(asRecord(action).command ?? "").includes("gh auth status")), "missing-token branch should recommend gh auth status", missingTokenActions);
assertCondition(missingTokenActions.some((action) => String(asRecord(action).command ?? "").includes("gh pr create") && asRecord(action).writesRemote === false), "missing-token branch should recommend PR create dry-run without writes", missingTokenActions);