Skip to content

feat(daemon): opt-in OS ceiling on terminal processes, reported per host - #27769

Merged
OrcaWin merged 4 commits into
mainfrom
np/d1d-ceiling
Oct 12, 2026
Merged

OrcaWin merged 4 commits into
mainfrom
np/d1d-ceiling

Conversation

@OrcaWin

@OrcaWin OrcaWin commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator
Files Added Deleted Net
Test 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​229 $\color{#cf222e}{\Huge{\mathbf{−}}}$​5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​224
Prod 15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​520 $\color{#cf222e}{\Huge{\mathbf{−}}}$​27 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​493

ELI5

A runaway build or agent inside a terminal can fork thousands of processes and take the whole machine down; #10493 was a kernel panic. Orca already limits how many agents it restores at once, but that is a software limit, and it can't stop what a running agent forks afterwards. This PR adds an optional kernel-enforced cap on the processes that terminals can run. It is off by default: it should be switched on by default only after saturation testing shows the terminal host stays responsive while the cap is being hit.

What Changed

  • Switch: ORCA_TERMINAL_PROCESS_CEILING=<n> (64 or more) turns it on when the terminal daemon starts. Unset or 0 means off; a smaller or non-numeric value is reported as off with the reason invalid limit.
  • Linux:
    • At start, the daemon asks the user's systemd manager (over busctl) for one transient slice, orca_ptys_<launch>.slice, with TasksMax=<n>.
    • Each new terminal then execs through systemd-run --user --scope --slice=… into its own scope under that slice. systemd-run --scope execs in place, so the pid, argv and exit status are unchanged. Once the slice holds <n> tasks, a fork inside it fails with EAGAIN.
    • The slice is a sibling of the daemon's own scope, never its parent. pids.max counts threads, and the daemon has to keep serving sockets, attach and cancel while a build in a terminal hits the cap. The name has no dashes, because systemd would read dashes as nesting.
  • Degrades cleanly: without systemd, a reachable user bus, or a user manager that outlives logout (linger), the daemon reports off (no user manager). It uses the same capability probe as the daemon's durable scope. If the slice can't be created, it reports off (slice unavailable). In both cases terminals spawn exactly as before.
  • Windows:
    • The patched node-pty gains setActiveProcessLimit(n). When it is set, every later ConPTY job also gets JOB_OBJECT_LIMIT_ACTIVE_PROCESS, so the cap applies to each terminal's tree, not to all terminals together.
    • A build without the new export reports off (native unavailable).
    • Processes that native shells explicitly break away from the job (already allowed for installers) escape the limit.
  • macOS: off (unsupported platform). The design keeps RLIMIT_NPROC as a prototype for later.
  • Reporting, per host:
    • The daemon writes processCeiling (on with limit and mechanism, or off with a reason) into its pid record, next to cgroupUnit.
    • orcad health carries it as an optional terminalDaemon.processCeiling.
    • orca serve's human readiness gains a line such as Terminal process ceiling: off (disabled).
    • A daemon older than this change shows as "not reported".
  • orca serve --help documents the switch.

Why

The software caps already landed (live automatic-restore cap, total terminal cap, growth breaker), but only the kernel can guarantee a bound. The owner's decision (D1 Q4) was:

  • PTYs go in a sibling scope, not in the daemon's scope.
  • Windows uses a job limit.
  • Every host reports on or off with a reason.
  • It stays off by default until saturation tests pass.

Rejected alternatives:

  • Add TasksMax to the daemon's own scope (the original sketch). That would count the daemon's threads and let a fork bomb starve the daemon.
  • Move each shell into the slice after it spawns. A fast child could escape in the gap.

Exec'ing through systemd-run places the shell before it can fork.

Linked Issue

Part of the headless-serve robustness work (C5 / Q4), toward #10493.

Visual Proof

N/A. Off by default, with no UI. The only visible change is one extra readiness line in orca serve output.

Testing

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

  • pty-process-ceiling.test.ts covers:

    • off by default, and invalid limits;
    • no user manager, which must not touch systemd;
    • macOS unsupported;
    • the slice request uses TasksMax and a top-level name, and a failed request degrades;
    • the Windows native setter present and absent;
    • an untouched spawn when off, and the systemd-run wrap (argv kept, Chromium's disabled bus removed);
    • the pid-record round trip, with old records reading as "not reported".
  • No real systemd or Windows job was exercised here. That, and the saturation test (keep the daemon serving while a terminal hits the cap), are why it ships off.

  • The Windows native change compiles in CI's Windows build. When the switch is off it is inert: the static limit stays 0.

pnpm tc, oxlint and check:code-quality:changed pass locally; pnpm lint is covered by CI.

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • SSH: the decision runs on the execution host's daemon. An orcad host over SSH reports its own ceiling in its health and readiness, so a remote host's cap is never confused with the local one.
  • Wire compatibility: only new optional fields. Old clients ignore them, and old daemons simply don't report.
  • Not done here, and still needed before default-on:
    • the saturation tests;
    • a "terminal ceiling reached" notice driven by pids.events;
    • an aggregate Windows limit, meaning a parent job above the per-terminal jobs.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 155da30e-0ae1-48a6-8f51-dcaeecc6784a

📥 Commits

Reviewing files that changed from the base of the PR and between b1a4249 and 230912a.


📒 Files selected for processing (1)
  • src/main/daemon/daemon-health.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.



📝 Walkthrough

Walkthrough

The change adds an optional PTY process ceiling. Linux uses a systemd slice and per-terminal scopes. Windows uses ConPTY job objects. The daemon initializes the ceiling, applies it to PTY spawning, and records its state in daemon metadata. Health and readiness output report the state, and the serve command documents the setting. ConPTY adds synchronized job lifecycle handling and APIs for process-tree termination, process ID listing, host-process assignment, and active process limits.


Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 23091

The opt-in process ceiling can be reported as active if its cgroup limit changes while the control file remains. This is a bounded operational risk; be aware that the reported status does not verify the configured value.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 26 functions across 15 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: an opt-in OS-enforced process ceiling for terminal processes.
Description check Passed The description covers the required sections, user impact, implementation, rationale, testing, compatibility, and checklist items. It documents issue #10493, although it does not use the template’s ex…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2af748d0-7f27-4fb9-9d51-0951ae77dfff
📥 Commits

Reviewing files that changed from the base of the PR and between 6c3ce44 and d74b886.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • config/patches/node-pty@1.1.0.patch
  • src/cli/specs/serve.ts
  • src/main/daemon/daemon-cgroup-scope.ts
  • src/main/daemon/daemon-entry.ts
  • src/main/daemon/daemon-pid-file-parse.ts
  • src/main/daemon/daemon-scope-death-watch.ts
  • src/main/daemon/daemon-spawner.ts
  • src/main/daemon/pty-process-ceiling-runtime.ts
  • src/main/daemon/pty-process-ceiling-state.ts
  • src/main/daemon/pty-process-ceiling.test.ts
  • src/main/daemon/pty-process-ceiling.ts
  • src/main/daemon/pty-subprocess/native-pty-spawn.ts
  • src/main/orcad/orcad-health.ts
  • src/main/server/serve-readiness.ts
  • src/main/windows/windows-pty-job.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.

Comment on lines 912 to 923
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobLimits{};
+ jobLimits.BasicLimitInformation.LimitFlags =
+ handle->allowJobBreakaway ? JOB_OBJECT_LIMIT_BREAKAWAY_OK : 0;
+ // Orca: an opt-in ceiling; forks past it fail inside this terminal's tree only.
+ const uint32_t activeProcessLimit = ptyActiveProcessLimit.load();
+ if (activeProcessLimit > 0) {
+ jobLimits.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
+ jobLimits.BasicLimitInformation.ActiveProcessLimit = activeProcessLimit;
+ }
+ if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jobLimits, sizeof(jobLimits)) ||
+ !AssignProcessToJobObject(hJob, piClient.hProcess)) {
+ // Why tolerate failure: an outer job without JOB_OBJECT_LIMIT_BREAKAWAY_OK

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '875,935p' config/patches/node-pty@1.1.0.patch
sed -n '1210,1245p' config/patches/node-pty@1.1.0.patch
sed -n '93,125p' src/main/daemon/pty-process-ceiling.ts

Repository: stablyai/orca

Length of output: 6466


🏁 Script executed:

set -eu
printf '%s\n' '--- relevant symbols ---'
rg -n -F --glob '*.{cc,js,ts}' -- 'setActiveProcessLimit' . || true
rg -n -F --glob '*.{cc,js,ts}' -- 'setWindowsLimit' . || true
rg -n -F --glob '*.{cc,js,ts}' -- 'PtyConnect' . || true
rg -n -F --glob '*.{cc,js,ts}' -- 'hJob' . || true
printf '%s\n' '--- Windows native patch around job setup and setter ---'
sed -n '820,960p' config/patches/node-pty@1.1.0.patch
printf '%s\n' '--- TypeScript resolver and Windows setup ---'
sed -n '1,240p' src/main/daemon/pty-process-ceiling.ts
printf '%s\n' '--- all resolver call sites and status consumers ---'
rg -n -F --glob '*.ts' -- 'resolvePtyProcessCeiling' src test tests . || true
rg -n -F --glob '*.ts' -- 'ptyProcessCeiling' src test tests . || true

Repository: stablyai/orca

Length of output: 29960


🏁 Script executed:

set -eu
printf '%s\n' '--- Windows job binding ---'
nl -ba src/main/windows/windows-pty-job.ts | sed -n '1,70p;210,255p'
printf '%s\n' '--- ceiling runtime initialization ---'
nl -ba src/main/daemon/pty-process-ceiling-runtime.ts | sed -n '1,90p'
printf '%s\n' '--- Windows ceiling tests ---'
nl -ba src/main/daemon/pty-process-ceiling.test.ts | sed -n '100,125p'

Repository: stablyai/orca

Length of output: 10081


Do not report the Windows ceiling as on after job setup falls back

setPtyActiveProcessLimit returns the native setter result, but the native setter only stores the limit for future jobs. If SetInformationJobObject or AssignProcessToJobObject later fails, PtyConnect closes hJob and resumes the shell without a job. The intentional fallback keeps the terminal running, but that shell can spawn descendants without JOB_OBJECT_LIMIT_ACTIVE_PROCESS while the daemon reports the ceiling as on.

When a limit is requested, make job setup failure fail PtyConnect through its existing cleanup path instead of resuming an unassigned shell. The runtime contract states that the ceiling is applied to every PTY spawn.

Comment on lines +1213 to +1238
+
+/**
+ * Orca: setActiveProcessLimit(limit) applies JOB_OBJECT_LIMIT_ACTIVE_PROCESS to
+ * every ConPTY job created afterwards. 0 turns it off. Existing jobs keep theirs.
+ */
+static Napi::Value PtySetActiveProcessLimit(const Napi::CallbackInfo& info) {
+ Napi::Env env(info.Env());
+ Napi::HandleScope scope(env);
+ if (info.Length() != 1 || !info[0].IsNumber()) {
+ throw Napi::Error::New(env, "Usage: pty.setActiveProcessLimit(limit)");
+ }
+ ptyActiveProcessLimit.store(info[0].As<Napi::Number>().Uint32Value());
+ return Napi::Boolean::New(env, true);
+}
+
/**
* Init
*/
@@ -577,6 +887,9 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
@@ -577,6 +910,10 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("resize", Napi::Function::New(env, PtyResize));
exports.Set("clear", Napi::Function::New(env, PtyClear));
exports.Set("kill", Napi::Function::New(env, PtyKill));
+ exports.Set("terminateJob", Napi::Function::New(env, PtyTerminateJob));
+ exports.Set("listJobProcessIds", Napi::Function::New(env, PtyListJobProcessIds));
+ exports.Set("assignCurrentProcessToJob", Napi::Function::New(env, PtyAssignCurrentProcessToJob));
+ exports.Set("setActiveProcessLimit", Napi::Function::New(env, PtySetActiveProcessLimit));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '40,60p' src/main/daemon/pty-process-ceiling.ts
sed -n '92,119p' src/main/daemon/pty-process-ceiling.ts
sed -n '1208,1240p' config/patches/node-pty@1.1.0.patch
sed -n '890,930p' config/patches/node-pty@1.1.0.patch

Repository: stablyai/orca

Length of output: 5564


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- resolver and callers ---'
rg -n -F --glob '*.{ts,tsx,js,cjs,mjs}' -- 'resolvePtyProcessCeiling' src tests config || true
rg -n -F --glob '*.{ts,tsx,js,cjs,mjs}' -- 'setActiveProcessLimit' src tests config || true
sed -n '1,240p' src/main/daemon/pty-process-ceiling.ts
printf '%s\n' '--- native symbol/storage and job application ---'
rg -n -F --glob 'config/patches/node-pty@1.1.0.patch' -- 'ptyActiveProcessLimit' config/patches/node-pty@1.1.0.patch
rg -n -F --glob 'config/patches/node-pty@1.1.0.patch' -- 'setActiveProcessLimit' config/patches/node-pty@1.1.0.patch
rg -n -F --glob 'setWindowsLimit' -- 'setWindowsLimit' src tests config || true
sed -n '1,90p' config/patches/node-pty@1.1.0.patch

Repository: stablyai/orca

Length of output: 11946


🏁 Script executed:

rg -n -F --glob '*.{ts,tsx,js,cjs,mjs}' -- 'resolvePtyProcessCeiling' src tests config || true
rg -n -F --glob '*.{ts,tsx,js,cjs,mjs}' -- 'setActiveProcessLimit' src tests config || true
sed -n '1,240p' src/main/daemon/pty-process-ceiling.ts
rg -n -F --glob 'config/patches/node-pty@1.1.0.patch' -- 'ptyActiveProcessLimit' config/patches/node-pty@1.1.0.patch
rg -n -F --glob 'config/patches/node-pty@1.1.0.patch' -- 'setActiveProcessLimit' config/patches/node-pty@1.1.0.patch
rg -n -F --glob 'setWindowsLimit' -- 'setWindowsLimit' src tests config || true
sed -n '1,90p' config/patches/node-pty@1.1.0.patch

Repository: stablyai/orca

Length of output: 11867


🏁 Script executed:

printf '%s\n' 'CALLERS'
rg -n -F --glob '*.ts' -- 'resolvePtyProcessCeiling' src || true
rg -n -F --glob '*.ts' -- 'setActiveProcessLimit' src || true
printf '%s\n' 'RESOLVER'
nl -ba src/main/daemon/pty-process-ceiling.ts | sed -n '40,180p'
printf '%s\n' 'NATIVE'
rg -n -F -- 'ptyActiveProcessLimit' config/patches/node-pty@1.1.0.patch
nl -ba config/patches/node-pty@1.1.0.patch | sed -n '880,940p'
nl -ba config/patches/node-pty@1.1.0.patch | sed -n '1208,1240p'

Repository: stablyai/orca

Length of output: 13367


🏁 Script executed:

rg -n -F --glob '*.ts' -- 'resolvePtyProcessCeiling' .
rg -n -F --glob '*.ts' -- 'setWindowsLimit' .
rg -n -F --glob '*.ts' -- 'setActiveProcessLimit' .

Repository: stablyai/orca

Length of output: 2745


Reject Windows ceilings above UINT32_MAX before calling the native setter.

parsePtyProcessCeiling accepts safe integers above the Windows 32-bit range. On Windows, the daemon passes the value to setActiveProcessLimit, which stores it with Uint32Value(). Thus, 4294967296 becomes 0, while the setter returns true. The resolver reports the ceiling as on, but job creation skips JOB_OBJECT_LIMIT_ACTIVE_PROCESS because the stored value is zero.

Suggested fix
   if (input.platform === 'win32') {
+    if (parsed.limit > 0xffffffff) {
+      return off('invalid_limit')
+    }
     return input.setWindowsLimit?.(parsed.limit)

Comment on lines +60 to +63
if (!pidsMaxExists(runtime.pidsMaxPath)) {
setPtyProcessCeilingForTest({ state: 'off', reason: 'slice_lost' }, null)
return { file, args, env }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

rg -n 'processCeiling|currentPtyProcessCeiling|write.*Pid|pidFile' src/main/daemon/daemon-entry.ts src/main/daemon/pty-process-ceiling-runtime.ts src/main/orcad/orcad-health.ts src/main/server/serve-readiness.ts src/main/daemon/daemon-spawner.ts
sed -n '40,110p' src/main/daemon/pty-process-ceiling-runtime.ts

Repository: stablyai/orca

Length of output: 3910


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- daemon-entry relevant blocks ---'
nl -ba src/main/daemon/daemon-entry.ts | sed -n '115,165p;270,315p'
printf '%s\n' '--- daemon-spawner relevant definitions and callers ---'
nl -ba src/main/daemon/daemon-spawner.ts | sed -n '1,55p;135,220p'
printf '%s\n' '--- runtime state helpers ---'
nl -ba src/main/daemon/pty-process-ceiling-runtime.ts | sed -n '1,75p'
printf '%s\n' '--- health reader and fallback ---'
nl -ba src/main/orcad/orcad-health.ts | sed -n '35,75p;140,180p'
printf '%s\n' '--- readiness reader and fallback ---'
nl -ba src/main/server/serve-readiness.ts | sed -n '85,130p'
printf '%s\n' '--- all pid record publication references ---'
rg -n -F -- 'publishDaemonPidFile(' src/main
rg -n -F -- 'replaceDaemonPidFile(' src/main

Repository: stablyai/orca

Length of output: 21845


🏁 Script executed:

set -o pipefail
nl -ba src/main/daemon/daemon-entry.ts | sed -n '115,165p;270,315p'
nl -ba src/main/daemon/daemon-spawner.ts | sed -n '1,55p;135,220p'
nl -ba src/main/daemon/pty-process-ceiling-runtime.ts | sed -n '1,75p'
nl -ba src/main/orcad/orcad-health.ts | sed -n '35,75p;140,180p'
nl -ba src/main/server/serve-readiness.ts | sed -n '85,130p'
rg -n -F -- 'publishDaemonPidFile(' src/main || test "$?" -eq 1
rg -n -F -- 'replaceDaemonPidFile(' src/main || test "$?" -eq 1

Repository: stablyai/orca

Length of output: 21603


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- publication callback definition and calls ---'
rg -n -F -- 'publishEndpointOwnership' src/main || test "$?" -eq 1
rg -n -F -- 'currentPtyProcessCeiling' src || test "$?" -eq 1
rg -n -F -- 'setPtyProcessCeilingForTest' src || test "$?" -eq 1
printf '%s\n' '--- daemon start implementation ---'
rg -n -F -- 'export async function startDaemon' src/main || test "$?" -eq 1
rg -n -F -- 'function startDaemon' src/main || test "$?" -eq 1
rg -n -F -- 'startDaemon(' src/main/daemon src/main | head -80

Repository: stablyai/orca

Length of output: 8863


🏁 Script executed:

set -o pipefail
nl -ba src/main/daemon/daemon-endpoint-lifecycle.ts | sed -n '1,100p'
nl -ba src/main/daemon/daemon-main.ts | sed -n '1,65p;155,190p'

Repository: stablyai/orca

Length of output: 7707


Republish the PID record when the ceiling is lost.

When wrapPtySpawnForCeiling detects a missing pids.max, it changes the runtime state to off (slice_lost) and starts the PTY without the ceiling. The PID record still contains the startup processCeiling value. orcad-health and orca serve can therefore continue to report on.

Republish the PID record after this transition. Use currentPtyProcessCeiling() and preserve the existing record fields and replacement semantics. The health reader has no current-runtime fallback.

Comment on lines +41 to +43
const tcc = wrapShellSpawnForMacosTccAttribution(shellPath, shellArgs, args.env)
// Linux with the opt-in ceiling: exec through systemd-run into the ceiling slice.
const wrapped = wrapPtySpawnForCeiling(tcc.file, tcc.args, args.env)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '30,100p' src/main/daemon/pty-subprocess/native-pty-spawn.ts
sed -n '40,105p' src/main/daemon/pty-process-ceiling-runtime.ts
rg -n 'systemd-run|spawnNativeDaemonPty|spawnAt' src/main/daemon/pty-subprocess src/main/daemon/pty-process-ceiling-runtime.ts

Repository: stablyai/orca

Length of output: 6353


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- native PTY spawn ---'
nl -ba src/main/daemon/pty-subprocess/native-pty-spawn.ts | sed -n '1,125p'
printf '%s\n' '--- ceiling runtime definitions and callers ---'
rg -n -F --glob '*.ts' -- 'wrapPtySpawnForCeiling' src
rg -n -F --glob '*.ts' -- 'spawnNativeDaemonPty' src
rg -n -F --glob '*pty-process-ceiling*' -- 'terminals continue' src docs . 2>/dev/null || true
printf '%s\n' '--- relevant ceiling tests and documentation references ---'
rg -n -F --glob '*.{ts,md}' -- 'systemd-run' src docs . 2>/dev/null || true
rg -n -F --glob '*.{ts,md}' -- 'slice_lost' src docs . 2>/dev/null || true
rg -n -F --glob '*.{ts,md}' -- 'process ceiling' src docs . 2>/dev/null || true

Repository: stablyai/orca

Length of output: 17502


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- PTY subprocess caller ---'
nl -ba src/main/daemon/pty-subprocess.ts | sed -n '1,190p'
printf '%s\n' '--- PTY spawn consumers and lifecycle handling ---'
rg -n -F --glob '*.ts' -- 'spawned.process' src/main/daemon src/main | head -80
rg -n -F --glob '*.ts' -- '.onExit' src/main/daemon/pty-subprocess.ts src/main/daemon src/main | head -100
printf '%s\n' '--- ceiling tests ---'
nl -ba src/main/daemon/pty-process-ceiling.test.ts | sed -n '1,230p'
printf '%s\n' '--- node-pty dependency declarations ---'
rg -n -F --glob 'package.json' -- 'node-pty' .
rg -n -F --glob 'pnpm-lock.yaml' -- 'node-pty@' .
printf '%s\n' '--- changed diff ---'
git diff --no-ext-diff --unified=40 6c3ce444bc36b5826733a4f064faab3df6f495ef d74b886c3bf811892f1c131c2d850c99292bdc07 -- src/main/daemon/pty-subprocess/native-pty-spawn.ts src/main/daemon/pty-process-ceiling-runtime.ts src/main/daemon/pty-subprocess.ts

Repository: stablyai/orca

Length of output: 32920


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- subprocess handle lifecycle ---'
nl -ba src/main/daemon/pty-subprocess/subprocess-handle.ts | sed -n '1,235p'
printf '%s\n' '--- node-pty 1.1.0 source or local package evidence ---'
find . -path '*/node-pty/*' -o -path '*/node_modules/node-pty*' | head -40
rg -n -F --glob '*.{ts,cpp,h,cc,md}' -- 'class UnixPtyProcess' . 2>/dev/null || true
rg -n -F --glob '*.{ts,cpp,h,cc,md}' -- 'spawn(file' . 2>/dev/null || true
printf '%s\n' '--- tests for native spawn failure and immediate exit ---'
rg -n -F --glob '*.test.ts' -- 'spawnNativeDaemonPty' src/main
rg -n -F --glob '*.test.ts' -- 'createPtySubprocess' src/main/daemon | head -80

Repository: stablyai/orca

Length of output: 23788


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- node-pty 1.1.0 Unix spawn implementation ---'
nl -ba node_modules/.pnpm/node-pty@1.1.0_patch_hash=*/node_modules/node-pty/lib/unixTerminal.js | sed -n '1,230p'
printf '%s\n' '--- node-pty 1.1.0 API declarations ---'
rg -n -F --glob '*.d.ts' -- 'spawn(' node_modules/.pnpm/node-pty@1.1.0_patch_hash=*/node_modules/node-pty
rg -n -F --glob '*.js' -- 'pty.fork' node_modules/.pnpm/node-pty@1.1.0_patch_hash=*/node_modules/node-pty/lib

Repository: stablyai/orca

Length of output: 11003


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- systemd manager failure contract ---'
nl -ba src/main/daemon/daemon-cgroup-scope.ts | sed -n '130,160p'
printf '%s\n' '--- repository wording for continued terminal spawning ---'
rg -n -i --glob '*.{md,mdx,ts,tsx}' -- 'terminals? (continue|continues|still|keep).*(spawn|start)|spawn.*terminals?|terminal.*ceiling|process ceiling' . 2>/dev/null || true

Repository: stablyai/orca

Length of output: 41476


Retry when the Linux ceiling wrapper fails.

wrapPtySpawnForCeiling checks only pids.max. If the user manager is unreachable, node-pty still returns an IPty for systemd-run; the wrapper then exits asynchronously. The Linux path has no retry, so createDaemonPtySubprocessHandle marks the new terminal dead instead of launching the shell unwrapped.

Handle a wrapper startup failure in native-pty-spawn.ts. Disable the ceiling and retry the original tcc.file and tcc.args once. A try/catch around nodePty.spawn is not sufficient because node-pty can return before systemd-run exits.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b5daa823-5ac5-4936-bb1d-08b3d723ec8a
📥 Commits

Reviewing files that changed from the base of the PR and between d74b886 and f40da9c.

📒 Files selected for processing (5)
  • src/main/daemon/daemon-scope-death-watch.ts
  • src/main/daemon/pty-process-ceiling-state.ts
  • src/main/daemon/pty-process-ceiling.test.ts
  • src/main/daemon/pty-process-ceiling.ts
  • src/main/orcad/orcad-health.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.

Comment on lines +89 to +91
return exists(join('/sys/fs/cgroup', state.controlGroup, 'pids.max'))
? state
: { state: 'off', reason: 'slice_lost' }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check the limit when confirming a slice.

If pids.max changes to max while the daemon runs, existsSync still returns true. Health then reports on although the slice no longer enforces state.limit. Read pids.max and compare its value with state.limit; report off when the values differ. The kernel defines max as an unlimited PID setting. (kernel.org)

@OrcaWin
OrcaWin merged commit d327cc9 into main Oct 12, 2026
71 checks passed
OrcaWin added a commit that referenced this pull request Oct 12, 2026
…ary (#28054)

#27756's script imported baseline helpers from check-focus-read-ratchet.mjs
after #27788 had moved them into ratchet-baseline.mjs, so it crashed on start
and failed every PR that merges main. Use the library's helpers, and record the
two daemon-process launches from #27769 that the broken check never saw.

Co-authored-by: Orca Worker <orca-worker@localhost>
brennanb2025 pushed a commit that referenced this pull request Oct 12, 2026
…ary (#28054)

#27756's script imported baseline helpers from check-focus-read-ratchet.mjs
after #27788 had moved them into ratchet-baseline.mjs, so it crashed on start
and failed every PR that merges main. Use the library's helpers, and record the
two daemon-process launches from #27769 that the broken check never saw.

Co-authored-by: Orca Worker <orca-worker@localhost>
brennanb2025 added a commit that referenced this pull request Oct 12, 2026
…27875)

* fix(browser): annotation card and outline follow the selected element

The grab overlay keeps re-measuring the picked element after the click
(scroll in any box, resize, element size changes), and the isolated
viewport bridge reports that live box so the pending card follows it,
including page zoom. The card's side pick uses its measured height.

* fix(browser): bind annotation card to selected element

* fix(browser): keep document annotation card on selection

* fix(ci): point the main sync-spawn ratchet at the shared ratchet library (#28054)

#27756's script imported baseline helpers from check-focus-read-ratchet.mjs
after #27788 had moved them into ratchet-baseline.mjs, so it crashed on start
and failed every PR that merges main. Use the library's helpers, and record the
two daemon-process launches from #27769 that the broken check never saw.

Co-authored-by: Orca Worker <orca-worker@localhost>

* fix(browser): follow selected element outer-box size

* refactor(chat): give the start-refusal resumable table its own module (#28085)

#27946 and #26684 together put agent-session-failure-words.ts at 304 lines,
over max-lines, which fails lint and the pre-commit hook on every merge of
main. The refusal-code table is standalone with one reader, so it moves out.

Co-authored-by: Orca Worker <orca-worker@localhost>

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: Orca Worker <orca-worker@localhost>
mkjung added a commit to mkjung/orca that referenced this pull request Oct 14, 2026
Upstream stablyai#27769 changed the Windows conpty section of
config/patches/node-pty@1.1.0.patch. This PR changes only the Unix
section, so the patch file merges without conflict. The lockfile took
upstream's version and pnpm 12.8.1 regenerated the node-pty patch hash
from the merged patch. The lockfile differs from upstream main only in
the three node-pty patch hash references. The PR's own changes are
unchanged from the previous head.

Validation: a frozen-lockfile install applied the merged patch and
rebuilt node-pty; the addon still imports cfsetispeed and cfsetospeed
at GLIBC_2.2.5. 309 tests passed across 15 focused files on the Node
runner, including upstream's PTY process ceiling tests; 11 Windows-
or macOS-only cases skipped. oxlint and oxfmt passed on the changed
scripts. The Bun runner and native prebuild builds were not rerun.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant