Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions memory/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

**πŸ“Œ Fast path: read `CURRENT-aaron.md` and `CURRENT-amara.md` first.** <!-- latest-paired-edit: fork-audit R/C/T diff-filter coverage + plumbing-vs-porcelain note (2026-04-29 round-10 Amara). NOTE: this comment is a single-slot "latest paired edit" marker (not a paired-edit log). Per the round-10 Amara framing the slot semantics are now explicit. -->

- [**Kernel-pipe vs JS-space stream ordering β€” TS+Bun port pattern (Otto, 2026-04-30)**](feedback_kernel_pipe_vs_js_space_stream_ordering_ts_bun_port_pattern_2026_04_30.md) β€” TS+Bun port discipline: when porting bash `$(... 2>&1)` to `spawnSync`, merge stdout+stderr via shell-side `bash -c "<cmd> 2>&1"` (preserves chronological ordering at the kernel pipe boundary), NOT `result.stdout + result.stderr` concat in JS-space (loses ordering when child interleaves writes). Origin: PR #901 slice-18 Copilot P1 round 2. Composes with `classifySpawnFailure` 4-case helper + Otto-363 substrate-or-it-didn't-happen.
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

P1: This new MEMORY.md index entry is far longer than the repo’s documented β€œkeep entries terse / ~200 chars” guidance, which makes the index harder to scan and increases the risk of truncation issues. Consider shortening this bullet to a title + very short hook and move the detailed rationale into the linked memory file body (which already contains it).

Suggested change
- [**Kernel-pipe vs JS-space stream ordering β€” TS+Bun port pattern (Otto, 2026-04-30)**](feedback_kernel_pipe_vs_js_space_stream_ordering_ts_bun_port_pattern_2026_04_30.md) β€” TS+Bun port discipline: when porting bash `$(... 2>&1)` to `spawnSync`, merge stdout+stderr via shell-side `bash -c "<cmd> 2>&1"` (preserves chronological ordering at the kernel pipe boundary), NOT `result.stdout + result.stderr` concat in JS-space (loses ordering when child interleaves writes). Origin: PR #901 slice-18 Copilot P1 round 2. Composes with `classifySpawnFailure` 4-case helper + Otto-363 substrate-or-it-didn't-happen.
- [**Kernel-pipe vs JS-space stream ordering β€” TS+Bun port pattern (Otto, 2026-04-30)**](feedback_kernel_pipe_vs_js_space_stream_ordering_ts_bun_port_pattern_2026_04_30.md) β€” Port bash `2>&1` to shell-side stderr/stdout merge in `spawnSync`; never concat `stdout` + `stderr` in JS.

Copilot uses AI. Check for mistakes.
- [**DST + code coverage are universal best practices for every Zeta language (Aaron 2026-04-30)**](feedback_dst_and_coverage_universal_every_language_aaron_2026_04_30.md) β€” Generalises Otto-272 / Otto-281 / Otto-273 to all languages. SQLSharp is the named TS+Bun reference. Pin seeds, fake clocks, no test retries; tests cover public API surface, CI surfaces coverage, reductions fail. Per-language tooling lives in the runtime layer (`docs/best-practices/`).
- [**Host mutation receipt β€” ruleset 15256879 code_quality rule removed (Aaron-authorized 2026-04-29)**](feedback_host_mutation_receipt_2026_04_29_ruleset_15256879_code_quality_removed.md) β€” Receipt for a live host (GitHub) mutation made before executable-host-settings tooling exists. PUT /repos/Lucent-Financial-Group/Zeta/rulesets/15256879 removed `code_quality severity=all` rule (host-side / non-git-declared CodeQL owner injecting `event=dynamic` "Code Quality" runs that bypassed the source-presence gate from PR #857). Made the git-visible advanced workflow `.github/workflows/codeql.yml` the sole CodeQL owner; resolved multi-master conflict that blocked PR #849. Aaron auth: *"if the org-recommended are legacy we can remove, declarative is better."* Per Amara *"Clickops used to restore declarative ownership must become a receipt, or it becomes the next drift"* β€” this receipt makes the live mutation visible to future executable-host-settings reconciler. NOT precedent for casual ruleset mutations; hook denial during episode was healthy; future apply path is host-reconciler-mediated with WorkClaim + policy + receipt; do NOT broaden `gh api ... rulesets/PUT` permission. Composes with executable-host-settings design packet, Otto-363, task #342 (completed) + #343.
- [**Standing authority β€” create public test git repos on AceHack + LFG, full admin, hourly billing tracking (Aaron, 2026-04-29)**](feedback_standing_authority_create_test_git_repos_public_only_track_billing_aaron_2026_04_29.md) β€” *"you have standing authority at any time to create git repos on acehack and lfg to test any features of git they just have to be public cause that's free... full admin... just track the billing every hour"* + clarification *"not noticing and stopping costs until we talk is the barrier, a mistaken accident spend is fine if you are auditing billing and catch the costs that way."* Standing grant: agent creates test repos on either org at any time (no per-creation Aaron sign-off), full admin to exercise any git/GitHub/CI/Actions/branch-protection/ruleset feature, with TWO binding constraints β€” keep test repos public so standard GitHub-hosted Actions / storage stay on the no-charge tier (private repos consume billed Actions minutes / storage / paid SKUs; the constraint avoids that billing mechanism, not "repo creation itself"; never create private), and hourly billing tracking must cover the new repos (audit-and-catch is the safety mechanism, not pre-perfect cost-zero). Failure mode is **silent spend**, not spend itself: audit-coverage is more load-bearing than spend-zero. Composes with Otto-365 "basically never ask" (test-repo creation IS invariant maintenance), branch-protection-settings-are-agent-call (delegated authority pattern), task #315 (hourly budget cadence β€” load-bearing safety latch), task #287 (cost visibility), AceHack mirror-not-peer doctrine (mirror constraint applies to AceHack/Zeta specifically; AceHack as ORG can host test repos), Aaron's visibility-constraint rule (test repos are inherently visible + billing surface = both legs hold).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
name: kernel-pipe vs JS-space stream ordering β€” TS+Bun port pattern (2026-04-30)
description: When porting bash command-substitution with `2>&1` to TypeScript via `spawnSync`, must merge stdout+stderr at the kernel pipe boundary (shell-side `2>&1`), NOT in JS-space by concatenating `result.stdout + result.stderr` β€” JS-space concat loses chronological ordering when child interleaves stdout/stderr writes.
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

P1: The frontmatter description reads like an absolute β€œnever concat stdout+stderr in JS-space”, but the body later calls out cases where appending result.stderr is necessary to surface shell-side parse errors. Consider tightening the wording here to clarify the rule is about preserving child stdout/stderr interleaving (kernel-pipe 2>&1) while optionally appending result.stderr for shell diagnostics when relevant.

Suggested change
description: When porting bash command-substitution with `2>&1` to TypeScript via `spawnSync`, must merge stdout+stderr at the kernel pipe boundary (shell-side `2>&1`), NOT in JS-space by concatenating `result.stdout + result.stderr` β€” JS-space concat loses chronological ordering when child interleaves stdout/stderr writes.
description: When porting bash command-substitution with `2>&1` to TypeScript via `spawnSync`, preserve child stdout/stderr interleaving by merging at the kernel pipe boundary (shell-side `2>&1`), not by reconstructing child output as `result.stdout + result.stderr` in JS-space; append `result.stderr` separately only when shell-side diagnostics such as parse errors must also be surfaced.

Copilot uses AI. Check for mistakes.
type: feedback
---

# kernel-pipe vs JS-space stream ordering β€” TS+Bun port pattern

When porting bash command-substitution like `output=$( script.sh 2>&1 )` to
TypeScript via `spawnSync`, the byte-equivalent shape is:

```typescript
const result = spawnSync("/bin/bash", ["-c", `"${path}" 2>&1`], {
encoding: "utf8",
stdio: ["inherit", "pipe", "pipe"],
});
Comment on lines +13 to +16
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

P1: The example interpolates path directly into the bash -c command string. Even if callers usually pass trusted paths, this pattern is easy to cargo-cult into contexts where the value is user-influenced, creating quoting/injection risk. Consider documenting the safer pattern of passing the path as a positional parameter to bash -c (so quoting is handled without string interpolation) and note that interpolation is only acceptable when the value is fully trusted/constructed.

Copilot uses AI. Check for mistakes.
const output = result.stdout ?? "";
```

NOT:

```typescript
// WRONG β€” loses chronological ordering when child interleaves stdout/stderr
const result = spawnSync(path, [], { encoding: "utf8" });
const output = result.stdout + result.stderr;
```

**Why:** bash `$(... 2>&1)` redirects stderr into stdout at the kernel pipe
boundary, which preserves the child's chronological write order across both
streams. JS-space concatenation glues two already-segregated buffers together
in a fixed order (`stdout` first, `stderr` second), losing the original
interleaving. If the child emits `[stdout-A, stderr-B, stdout-C]`, the
kernel-pipe form captures `A B C` in order; the JS-space form captures
`A C B`.

**How to apply:**

- **Always** use shell-side `2>&1` via `bash -c "<path> 2>&1"` for
byte-equivalent output to bash `$(... 2>&1)`. Use `/bin/bash` (not
`/bin/sh`) so bash-only features like `[[ ]]`, brace expansion, process
substitution still work.
- The performance cost (one extra fork+exec for `bash -c`) is negligible
compared to typical TS/CLI overhead.
- This applies to any TS port that needs in-order merged output β€” wrappers
spawning sibling scripts, audit scripts capturing combined diagnostic
output, peer-call wrappers piping LLM-CLI output through, etc.
- **Edge case β€” shell parse errors fall outside `( ) 2>&1`**: when wrapping
user-supplied commands like `(${cmd}) 2>&1`, shell parse errors emerge on
the parent shell's stderr (not the child's). For those, also concatenate
`result.stderr` to surface diagnostics. The two patterns are stacked, not
alternative β€” kernel-pipe for in-order child output, plus stderr-concat for
shell-side parse errors.
- Defensive: `result.stdout ?? ""` because the typings claim `string` when
encoding is set, but runtime can return `null` if the child fails to start.

## Origin

PR #901 (slice 18 β€” `tools/budget/daily-cost-report.ts`) Copilot P1 round 2.
The first version of `runProjectRunway` concatenated `result.stdout + result.stderr` in JS-space.
Copilot caught that this loses ordering vs the bash original
`$( "$script_dir/project-runway.sh" 2>&1 )`. Fixed by switching to
`spawnSync("/bin/bash", ["-c", \`"${path}" 2>&1\`])` so the merge happens at
the kernel pipe boundary chronologically. Same pattern then baked into
slice 19 (`tools/budget/project-runway.ts`).

## Composes with

- `docs/best-practices/typescript.md` (TS+Bun discipline; pending #351 skill).
- `tools/peer-call/{grok,gemini,codex}.ts` `runContextCmd` helpers β€” same
shell-side-merge pattern via `(${cmd}) 2>&1 | head -c N` for
shell-side truncation.
- Otto-363 substrate-or-it-didn't-happen β€” this finding only lived in commit
messages and review threads until this memory file landed.
- `classifySpawnFailure` 4-case helper (status set / ENOENT / signal / other)
β€” same shape from PRs #887/#892/#894/#896-#902. Used together when the
port also needs failure classification.
Loading