From 5f382262903f9f373e084f7ed78ed128f769d757 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Wed, 11 Nov 2026 08:39:15 -0600 Subject: [PATCH] fix(build): report compiler signals in server typecheck layers (#20603) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Its development checks help contributors verify changes. > - The server typecheck runs the compiler once for each source layer. > - A compiler child can stop on a signal before it reports type errors. > - The wrapper returned failure without the signal or the affected layer. > - This pull request reports the signal, layer, command, and working directory. > - Contributors can identify an interrupted check from its output. ## Linked Issues or Issue Description **What happened?** The server typecheck returned exit 1 after a layer banner when a compiler child stopped on a signal. The wrapper did not report that signal. Related layer work: #18737, #18738, and #18739. A search of open and closed PRs found no duplicate signal-reporting fix. **Expected behavior** Report the actual termination signal and affected layer. Keep compiler output visible and return failure. Do not infer a memory failure from SIGKILL alone. **Steps to reproduce** Run a controlled compiler child that sends SIGTERM or SIGKILL to itself. Run it through typecheckLayers and the real runTsc subprocess path. The added tests do this without memory pressure or timed waits. **Paperclip version or commit** Reproduced on 7368a0b15c2f. **Deployment mode** Source checkout on Linux with Node v25.9.0. ## What Changed - Print a stderr diagnostic before the existing early return for a compiler signal. - Include the layer, signal, compiler command, and working directory. - Add real subprocess regressions for SIGTERM in below and SIGKILL in cycle. - Check that child stdout and stderr remain visible and later layers stop. - Document the diagnostic and the limit of what a signal proves. ## Verification - Red: the two new subprocess tests failed because stderr omitted the signal and layer. - Green: `cd server && pnpm exec vitest run scripts/typecheck-layers.test.mjs` passed all 47 tests. This includes real compiler success, declaration output, and compiler error diagnostics. - `git diff --check` passed. - `pnpm verify` returned exit 2. The wrapper is a broad input, so CI must supply broader coverage. - Preflight installed dependencies and built shared/plugin-sdk. It could not build Rust outputs because cargo is absent. Rust builds are outside the focused local test scope. - Final head: `fdd68d7e13751573c661e80a285be86f1a1420bc`. - [CI passed](https://github.com/paperclipai/paperclip/actions/runs/42867062133). Typecheck, build, test suites, and all eight E2E shards passed. - [Greptile gave 5/5](https://github.com/paperclipai/paperclip/pull/20603#issuecomment-6774642345) for this head. No actionable findings or open review threads remain. ## Risks - Low risk. Only the existing signal branch gains a diagnostic. Failure propagation and compiler output handling stay the same. - The controlled signal tests require POSIX signal behavior. Local verification used Linux. ## Model Used - OpenAI Codex, based on GPT-6, with reasoning, repository tools, and code execution. The exact deployment model ID is not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip --- doc/DEVELOPING.md | 5 ++++ server/scripts/typecheck-layers.mjs | 7 +++++- server/scripts/typecheck-layers.test.mjs | 32 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 1023f2f1414..494ef747545 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -1443,6 +1443,11 @@ The server typecheck builds only the Runner's TypeScript output. Use `pnpm --filter @paperclipai/paperclip-runner build` when a local check needs the Runner binary. +If a compiler child terminates on a signal, the wrapper reports the signal, +layer, command, and working directory on stderr, then stops with a nonzero exit. +Compiler stdout and stderr remain visible. A signal such as `SIGKILL` alone does +not establish that the process ran out of memory. + `src/__tests__/tsconfig.json` holds the options for the test-tree layers, so an editor and the typecheck report the same errors. Server tests reach Daytona plugin source, but the root install does not install that plugin's dependencies. So this diff --git a/server/scripts/typecheck-layers.mjs b/server/scripts/typecheck-layers.mjs index 7791e3671f6..ea775d40e67 100644 --- a/server/scripts/typecheck-layers.mjs +++ b/server/scripts/typecheck-layers.mjs @@ -288,7 +288,12 @@ export function typecheckLayers({ const config = path.join(OUTPUT_DIRECTORY, `tsconfig.${layer.name}.json`); const { status, signal } = run(["-p", config], { cwd: serverRoot }); // A killed process leaves no declarations, so later layers would only add noise. - if (signal) return status || 1; + if (signal) { + console.error( + `typecheck:layers: ${layer.name}: compiler terminated by ${signal} (tsc -p ${config}, cwd: ${serverRoot})`, + ); + return status || 1; + } // Later layers still run after type errors. Each file is checked in one // layer, so this reports everything a single tsc run would. if (status !== 0) exitCode = status; diff --git a/server/scripts/typecheck-layers.test.mjs b/server/scripts/typecheck-layers.test.mjs index 97ee651b6ce..8239fea0c4d 100644 --- a/server/scripts/typecheck-layers.test.mjs +++ b/server/scripts/typecheck-layers.test.mjs @@ -319,6 +319,38 @@ describe("typecheckLayers", () => { expect(status).toBe(1); expect(configs).toHaveLength(1); }); + + it.each([ + ["SIGTERM", "below"], + ["SIGKILL", "cycle"], + ])("reports %s and the affected %s layer from a real compiler child", (signal, layer) => { + const root = serverFixture(); + const bin = path.join(root, "node_modules", "typescript", "bin"); + mkdirSync(bin, { recursive: true }); + writeFileSync(path.join(bin, "tsc"), ` + const fs = require("node:fs"); + fs.writeSync(1, "compiler stdout\\n"); + fs.writeSync(2, "compiler stderr\\n"); + if (process.argv[3].endsWith("tsconfig.${layer}.json")) { + process.kill(process.pid, ${JSON.stringify(signal)}); + } + `); + const wrapper = new URL("./typecheck-layers.mjs", import.meta.url).href; + + const result = spawnSync(process.execPath, ["--input-type=module", "-e", ` + import { typecheckLayers } from ${JSON.stringify(wrapper)}; + process.exitCode = typecheckLayers({ serverRoot: ${JSON.stringify(root)} }); + `], { encoding: "utf8", timeout: 10_000 }); + + expect(result.error).toBeUndefined(); + expect(result.signal).toBeNull(); + expect(result.status).toBe(1); + expect(result.stdout).toContain("compiler stdout"); + expect(result.stderr).toContain("compiler stderr"); + expect(result.stderr).toContain(`typecheck:layers: ${layer}: compiler terminated by ${signal}`); + expect(result.stderr).toContain(`tsconfig.${layer}.json`); + expect(result.stdout).not.toContain("typecheck:layers: above"); + }); }); describe("typecheckLayers with the real compiler", () => {