Files
openclaw/scripts/close-duplicate-prs-after-merge.d.mts
e33257de11 fix(scripts): bound duplicate PR closure GitHub lookups (#110750)
* fix(scripts): bound duplicate PR closure GitHub lookups

The duplicate PR closure helper performs multiple sequential gh API
reads and writes after a merge. Each call used execFileSync without a
timeout, so one stalled GitHub request could block the surrounding
workflow job until it expires.

Add a 60-second timeout and SIGKILL to the default gh runner, applied
to both read-only lookups and pr edit writes that forward stdin.

* test(scripts): add real behavior proof for duplicate PR closure timeout

Add two live subprocess tests using the real spawnSync (not mocked) to
prove the timeout + SIGKILL surface works against actual child
processes:

- A 1 ms timeout against a long-running node subprocess proves
  SIGKILL is delivered and the timeout triggers correctly.
- A fast node -e 0 subprocess with the production 60-second bound
  proves the timeout does not false-positive on healthy completions.

Switch the vi.mock to a partial mock using importOriginal so
spawnSync remains the real implementation for live tests while
execFileSync stays mocked for the DI-based option assertions.

* refactor(scripts): inject duplicate closure GitHub runner

* fix(scripts): declare trusted tooling pin validator

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-19 00:01:23 +01:00

87 lines
1.9 KiB
TypeScript

/**
* Parses comma-separated PR numbers from CLI/env input.
*/
export function parsePrNumberList(value: unknown): number[];
/**
* Parses duplicate PR close workflow arguments.
*/
export function parseArgs(
argv: string[],
env?: NodeJS.ProcessEnv,
):
| {
apply: boolean;
duplicates: number[];
help: true;
labels: string[];
landedPr?: number;
repo: string;
}
| {
apply: boolean;
duplicates: number[];
help?: undefined;
labels: string[];
landedPr: number;
repo: string;
};
/**
* Runs a GitHub CLI command with the workflow timeout policy.
*/
export function defaultRunGh(
args: string[],
options?: { input?: string },
params?: {
execFileSyncImpl?: (
command: string,
args: string[],
options: {
encoding: "utf8";
input?: string;
killSignal: "SIGKILL";
stdio: ["ignore", "pipe", "inherit"] | ["pipe", "pipe", "inherit"];
timeout: number;
},
) => string;
},
): string;
/**
* Parses changed hunk ranges from unified diff text.
*/
export function parseUnifiedDiffRanges(diffText: unknown): Map<unknown, unknown>;
/**
* Builds the close/skip plan for duplicate PR candidates.
*/
export function buildDuplicateClosePlan({
candidates,
diffs,
landed,
repo,
}: {
candidates: unknown;
diffs: unknown;
landed: unknown;
repo: unknown;
}): Array<Record<string, unknown>>;
/**
* Applies labels/comments/closes for planned duplicate PR actions.
*/
export function applyClosePlan({
labels,
plan,
repo,
runGh,
}: {
labels?: string[] | undefined;
plan: unknown;
repo: unknown;
runGh: unknown;
}): void;
/**
* Runs the duplicate PR close workflow.
*/
export function runDuplicateCloseWorkflow(
args: unknown,
runGh?: (args: string[], options?: Record<string, unknown>) => string,
): Array<Record<string, unknown>>;