Skip to content

fix(proc): address kz8.2 review comments (kz8.2.1)#7

Merged
brandon-fryslie merged 1 commit into
mainfrom
bf/kz8.2-review-comments
May 16, 2026
Merged

fix(proc): address kz8.2 review comments (kz8.2.1)#7
brandon-fryslie merged 1 commit into
mainfrom
bf/kz8.2-review-comments

Conversation

@brandon-fryslie

Copy link
Copy Markdown
Contributor

Summary

Addresses all 8 inline comments from Copilot's review of #6 (kz8.2). PR #6 merged before the bot finished reviewing — this PR closes the loose end without re-opening the original ticket. Followup tracked as brandon-process-discipline-kz8.2.1.

The 8 findings, in commit order:

  1. install category mislabelosacompile and lsregister were tagged as install.plutil. Added install.osacompile and install.lsregister to LAUNCH_CATEGORIES and tagged correctly. Per-binary metering now distinguishes the three install helpers.

  2. signal vs timeout — both launch and launchSync reported any signal-killed exit as reason: "timeout". Now distinguishes: "timeout" (our local timer fired and SIGTERMed the child), "signal" (OS/external killer — SIGKILL, SIGINT through tty group, SIGPIPE, SIGHUP, external kill), "non-zero" (clean exit, non-zero code), "spawn-error" (failure before child started). Async path tracks timedOut: boolean; sync path compares against opts.timeoutMs + result.signal === "SIGTERM".

  3. silent detached-spawn failurevoid launch({detached: true}) in spawnDaemonDetachedReal discarded the Promise, so a failed daemon spawn (ENOENT, EACCES) was reported as success. Exported new launchDetachedSync that returns LaunchResult synchronously; spawnDaemonDetachedReal now returns result.ok. Also: spawn() doesn't throw for ENOENT — it returns a ChildProcess with pid: undefined and emits 'error' asynchronously. launchDetachedSync now attaches a no-op error listener and treats pid === undefined as spawn-error. A new test covers this case.

  4. rolling-window ring buffer cap — bumped ROLLING_BUFFER_CAP from 4096 to 16384 (~273 sustained launches/sec before undercount kicks in). Cap rationale documented at the constant. The old comment "old entries get overwritten; 'last minute' truth is preserved" was wrong and has been corrected.

  5. execGitAsync(args: string[]) — the prior command: string + whitespace-split contract was fragile for any future caller passing a quoted arg or a path with spaces. Signature now takes args: readonly string[]; 12 call sites updated to pass arrays. The runtime bin === "git" check is gone (the boundary type carries it).

  6. byCategory / histograms symmetrybyCategory was built from all LAUNCH_CATEGORIES (every category present, zeros included) while p50DurationMs / p99DurationMs only included executed categories. Now byCategory matches: only executed categories appear. Same shape across all three subprocess maps, and matches the formatter's already-existing filter. JSON consumers wanting the full closed list read LAUNCH_CATEGORIES from src/proc/launch.

  7. dynamic category column paddingpadEnd(13) was too narrow for var-system.git (14), install.plutil (14), install.osacompile (18), terminal-width (14), etc. Now computed from Math.max(13, ...activeCats.map(c => c.length)).

  8. kill-after-settled race — timeout timer could fire and SIGTERM an already-settled child (e.g. extremely fast /bin/true that exits before setTimeout is scheduled). Guarded with if (!settled) child.kill("SIGTERM") and let timedOut = true set before the kill so the close handler's reason discrimination still works.

All changes cite the relevant [LAW:...] markers inline.

Test plan

  • pnpm typecheck — clean
  • pnpm lint — clean
  • pnpm check:protocol — TS/Rust agree
  • pnpm build — produces dist/
  • pnpm test — 32/32 suites, 682/682 tests pass (was 669; added two new tests for launchDetachedSync spawn-error + metering, and one for "signal" reason)
  • Manual smoke under multi-session render load (optional follow-up)

Refs: brandon-process-discipline-kz8.2.1

Eight findings from Copilot's review of #6, addressed in one PR:

1. install.osacompile / install.lsregister now have their own LAUNCH_CATEGORIES
   entries; per-binary metering no longer collapses three distinct install
   helpers into install.plutil.

2. launch / launchSync distinguish "timeout" (our local timer fired) from
   "signal" (OS/external killer ended the child). A signal-killed no-timeout
   child no longer misreports as timeout in daemon-stats.

3. launchDetachedSync is now exported and returns the typed outcome
   synchronously. spawnDaemonDetachedReal uses it and returns result.ok, so
   ENOENT / EACCES on detached daemon spawns surfaces instead of being
   reported as success. As a bonus, launchDetachedSync now detects ENOENT
   (which spawn doesn't throw for — it surfaces as pid=undefined).

4. Rolling-window ring buffer cap bumped to 16384 (~273 sustained
   launches/sec before undercount kicks in); cap rationale documented at
   the constant.

5. execGitAsync takes args: string[] directly; the prior whitespace-split
   contract is gone. Paths with spaces / quoted args can no longer
   mis-tokenize at the boundary.

6. byCategory now only includes executed categories — symmetric with
   p50/p99 histograms and with the formatter's filter. Consumers wanting
   the full enum read LAUNCH_CATEGORIES.

7. Stats formatter column width is computed from active categories
   (max(13, ...lengths)) instead of a hardcoded padEnd(13) that misaligned
   for var-system.git, install.plutil, terminal-width, and others.

8. Timer's child.kill is guarded by !settled so a very-fast child that
   exited before setTimeout was scheduled doesn't get a stray SIGTERM.

[LAW:single-enforcer] [LAW:one-type-per-behavior] [LAW:no-silent-fallbacks]
[LAW:dataflow-not-control-flow] [LAW:types-are-the-program]

Refs: brandon-process-discipline-kz8.2.1
Copilot AI review requested due to automatic review settings May 16, 2026 16:04
@brandon-fryslie brandon-fryslie merged commit a616a17 into main May 16, 2026
8 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR follows up on subprocess launch/metering review feedback by tightening launch result classification, detached daemon spawning, install categories, and daemon stats formatting.

Changes:

  • Adds distinct launch categories and a signal failure reason.
  • Introduces launchDetachedSync and uses it for daemon spawning.
  • Converts git command execution to argv arrays and adjusts subprocess stats output/formatting.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/proc/launch.ts Updates launch result reasons and adds detached sync launch API.
src/daemon/acquire.ts Uses detached sync launch for daemon spawning.
src/daemon/stats.ts Changes rolling cap and sparse subprocess category snapshot behavior.
src/daemon/client-stats.ts Adjusts subprocess category formatting and padding.
src/segments/git.ts Switches git execution from command strings to argv arrays.
src/install/index.ts Assigns more specific install subprocess categories.
test/proc/launch.test.ts Adds launch signal and detached sync coverage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/proc/launch.ts
Comment on lines +245 to +254
// spawnSync result, not in the surrounding control flow. Node sets
// `result.signal` whenever the child died from a signal — including but
// not limited to the timeout's SIGTERM. We can only attribute "timeout"
// when a timeout was actually requested; otherwise the signal came from
// somewhere else (OOM killer, ctrl-C through the tty group, etc.).
const hasTimeout = opts.timeoutMs !== undefined && opts.timeoutMs > 0;
const reason: "timeout" | "signal" | "non-zero" = result.signal
? hasTimeout && result.signal === "SIGTERM"
? "timeout"
: "signal"
Comment thread src/daemon/stats.ts
Comment on lines +158 to +160
// actually executed — same shape as p50/p99 below. Consumers wanting the
// full closed list can read LAUNCH_CATEGORIES (exported from src/proc/launch)
// and treat missing keys as zero.
Comment thread src/daemon/stats.ts
Comment on lines +162 to +163
for (const [cat, n] of this.subprocessCount) {
if (n > 0) byCategory[cat] = n;
Comment on lines +139 to +141
// Snapshot already includes only executed categories (stats.ts:
// snapshotSubprocesses keeps byCategory and the histograms symmetric).
const activeCats = Object.entries(s.subprocesses.byCategory);
Comment thread src/daemon/acquire.ts
category: "daemon-spawn",
});
return true;
return result.ok;
Comment thread src/proc/launch.ts
// — that pattern discards the spawn outcome and reports success on failure.
// This function returns the typed result synchronously, and also meters
// through the stats handle so detached spawns show up in daemon-stats.
export function launchDetachedSync(opts: LaunchOpts): LaunchResult {
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.

2 participants