refactor: extract shared process_runner.cjs from claude and copilot harnesses#29888
refactor: extract shared process_runner.cjs from claude and copilot harnesses#29888
Conversation
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ed8c699b-18bb-4843-9921-683c3fb4ad61 Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors the Claude and Copilot harnesses to share a single runProcess implementation (plus formatDuration / sleep) and adds a dedicated test suite for the shared runner.
Changes:
- Added
actions/setup/js/process_runner.cjsexporting sharedrunProcess,formatDuration, andsleep. - Updated
claude_harness.cjsandcopilot_harness.cjsto use the new object-parameterrunProcessAPI (with caller-provided logging and optional redactedlogArgs). - Added
process_runner.test.cjscovering duration formatting, sleep, andrunProcesslifecycle behavior.
Show a summary per file
| File | Description |
|---|---|
| actions/setup/js/process_runner.cjs | Introduces shared process spawning/forwarding/output-collection logic used by both harnesses. |
| actions/setup/js/process_runner.test.cjs | Adds Vitest coverage for the shared runner utilities and lifecycle behavior. |
| actions/setup/js/claude_harness.cjs | Removes duplicated helpers and switches to the shared runner with harness-specific logging/redaction. |
| actions/setup/js/copilot_harness.cjs | Removes duplicated helpers and switches to the shared runner; moves --prompt/-p redaction to call site via logArgs. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
actions/setup/js/process_runner.test.cjs:79
- These tests spawn real Node processes that write to the parent process stdout/stderr via
runProcess’s forwarding (process.stdout.write/process.stderr.write). This will make the Vitest output noisy and can interfere with CI logs; consider stubbingprocess.stdout.write/process.stderr.write(viavi.spyOn) during therunProcesstest suite and restoring afterward.
it("collects stdout output and sets hasOutput", async () => {
const logs = [];
const result = await runProcess({
command: process.execPath,
args: ["-e", 'process.stdout.write("hello stdout"); process.exit(0)'],
attempt: 0,
log: msg => logs.push(msg),
});
expect(result.hasOutput).toBe(true);
expect(result.output).toContain("hello stdout");
});
- Files reviewed: 4/4 changed files
- Comments generated: 3
| const start = Date.now(); | ||
| await sleep(50); | ||
| expect(Date.now() - start).toBeGreaterThanOrEqual(40); | ||
| }); | ||
|
|
||
| it("resolves immediately for 0ms", async () => { | ||
| await expect(sleep(0)).resolves.toBeUndefined(); |
| // Extract the args portion from the log line (everything after "spawning: <cmd> ") | ||
| const afterCommand = spawnLog?.replace(/.*spawning: \S+ /, "") ?? ""; |
| child.on("exit", (code, signal) => { | ||
| log(`attempt ${attempt + 1}: process exit event` + ` exitCode=${code ?? 1}` + (signal ? ` signal=${signal}` : "")); | ||
| }); | ||
|
|
||
| // Resolve on 'close', not 'exit', to ensure stdio streams are fully drained. | ||
| child.on("close", (code, signal) => { | ||
| const durationMs = Date.now() - startTime; | ||
| const exitCode = code ?? 1; | ||
| log(`attempt ${attempt + 1}: process closed` + ` exitCode=${exitCode}` + (signal ? ` signal=${signal}` : "") + ` duration=${formatDuration(durationMs)}` + ` stdout=${stdoutBytes}B stderr=${stderrBytes}B hasOutput=${hasOutput}`); | ||
| resolve({ exitCode, output: collectedOutput, hasOutput, durationMs }); | ||
| }); | ||
|
|
||
| child.on("error", err => { | ||
| const durationMs = Date.now() - startTime; | ||
| // prettier-ignore | ||
| const errno = /** @type {NodeJS.ErrnoException} */ (err); | ||
| const errCode = errno.code ?? "unknown"; | ||
| const errSyscall = errno.syscall ?? "unknown"; | ||
| log(`attempt ${attempt + 1}: failed to start process '${command}': ${err.message}` + ` (code=${errCode} syscall=${errSyscall})`); | ||
| resolve({ | ||
| exitCode: 1, | ||
| output: collectedOutput, | ||
| hasOutput, | ||
| durationMs, | ||
| }); | ||
| }); |
|
@copilot review all comments |
🧪 Test Quality Sentinel ReportTest Quality Score: 94/100✅ Excellent test quality
Test Classification DetailsView all 19 test classifications
Flagged Tests — Requires ReviewNo tests flagged. All tests verify observable behavioral contracts using real process execution. Language SupportTests analyzed:
No Go test files were added or modified in this PR. Verdict
📖 Understanding Test ClassificationsDesign Tests (High Value) verify what the system does:
Implementation Tests (Low Value) verify how the system does it:
Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators. References: §25273924690
|
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/9fc604ee-8594-45bc-aaa2-7361445da690 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Addressed all three review comments in commit 9810f54:
|
…arnesses (#29888) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
claude_harness.cjsandcopilot_harness.cjseach contained an independentrunProcessimplementation (~50 lines) plus duplicatedformatDurationandsleephelpers — identical lifecycle logic that had already begun to drift in minor ways.Changes
New
process_runner.cjs— shared utility exportingrunProcess({ command, args, attempt, log, logArgs }),formatDuration, andsleeplogis caller-supplied so each harness keeps its own prefix ([claude-harness]/[copilot-harness])logArgs(optional) accepts a pre-redacted arg list for logging, keeping sensitive values out of logs without coupling redaction logic to the runnerclaude_harness.cjs— removed ~95 lines of local duplicates; updated call site to new object-param APIcopilot_harness.cjs— removed ~100 lines of local duplicates (including now-unusedspawnimport); moved--prompt/-predaction from insiderunProcessto the call site, passing it aslogArgsNew
process_runner.test.cjs— 19 tests coveringformatDuration,sleep, and the fullrunProcesslifecycle: stdout/stderr forwarding,hasOutputtracking, exit code propagation,close-based resolution, spawn error handling,logArgsredaction, and log truncation