Skip to content

test(cli): run ordinary run-command semantics in process - #2476

Closed
UncertaintyDeterminesYou4ndMe wants to merge 1 commit into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:perf/2387-cli-in-process
Closed

test(cli): run ordinary run-command semantics in process#2476
UncertaintyDeterminesYou4ndMe wants to merge 1 commit into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:perf/2387-cli-in-process

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

Part of #2387 (CLI workspace; the Headless workspace follows in a separate PR per the issue).

Problem

run-command.test.ts paid one Node subprocess startup per assertion — 29 spawns across 27 tests — including for scenarios that only exercise argument handling, session selection, and outcome-to-exit-code mapping. Those semantics flow entirely through the injectable MakaRunDeps seam (createContext, listSessions, stdinIsTTY, readStdin, writeStdout, writeStderr), so the subprocess added cost without adding a distinct failure signal.

Change

  • One fake, two routes. The scenario fake moves out of run-command-fixture.ts into run-command-fake.ts, parameterized by an options object instead of MAKA_RUN_* environment variables. The fixture becomes a thin subprocess wrapper over the same fake, so the in-process and subprocess routes cannot drift apart.
  • 21 ordinary-semantics tests move in process through runMakaTextCli with injected stdin/stdout/stderr. Assertions are unchanged.
  • No production changes.

Retained process contracts (real subprocess, unchanged assertions)

Contract Tests
Piped non-TTY stdin (run -, implicit stdin prompt, positional + stdin context) 3
SIGINT delivery → exit 130, empty stdout through a real pipe 2
Fail-closed sandbox boundary reaching non-interactive run 1

Exit codes 0/1/2 and stdout content through a real pipe remain asserted by these six; runtime-bootstrap.test.ts (real background ShellRun lifecycle) and the pi-tui-runner signal/fatal probes are intentionally untouched — their subject is the real process boundary.

One observable-signal fix

The graph-runtime-error scenario asserted "graph wait was never called" via a marker the fixture wrote to the real process.stderr. In process, that channel is not captured, which would have made the negative assertion vacuous. The marker now travels through the thrown error message (graph-wait-called: unexpected graph wait after failed invocation), which the CLI's error path surfaces on the injected stderr — the assertion keeps a live failure signal on both routes.

Timing

node --test dist/__tests__/run-command.test.js, same machine, warm build:

tests wall time
before 34 15.06s
after 34 3.60s (−76%)

34/34 pass across 5 consecutive rounds; full packages/cli suite 516/516. Typecheck, biome, and knip are clean (knip delta verified against a stashed baseline: the only change is the new fake counting as used).

cc @Astro-Han

Part of apache#2387. The maka run test suite paid a Node subprocess startup per
assertion, including for scenarios that only exercise argument handling,
session selection, and outcome-to-exit-code mapping through the injectable
MakaRunDeps seam.

- Extract the scenario fake from run-command-fixture.ts into
  run-command-fake.ts, parameterized by an options object instead of
  environment variables; the fixture becomes a thin subprocess wrapper
  over the same fake, so the two routes cannot drift apart.
- Move 21 ordinary-semantics tests in process through runMakaTextCli with
  injected stdin/stdout/stderr; assertions are unchanged.
- Retain real-subprocess coverage for every distinct process contract:
  piped non-TTY stdin (3), SIGINT delivery with exit 130 (2), and the
  fail-closed sandbox boundary path plus exit codes and stdout observed
  through a real process boundary (1).
- The graph-runtime-error scenario's "graph wait must not run" marker now
  travels through the thrown error message so the negative assertion stays
  observable on the captured stderr channel.

Timing (node --test dist/__tests__/run-command.test.js, local):
before 15.06s, after 3.60s. 34/34 tests pass, 5 consecutive rounds.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Rebased onto main after #2462. One deliberate resolution to note: #2462 added a processContractStderr filter so the two stderr-empty assertions tolerate Node's SQLite ExperimentalWarning. Both of those assertions now run in process, where the injected stderr channel sees only the CLI's own writes — no runtime warning can reach it. So the rebase drops the filter and restores the strict assert.equal(result.stderr, ''), which is both stronger (the filter also swallowed blank lines) and un-flaky by construction. #2462's intent is fully preserved, just by seam rather than by filtering. 34/34 across 3 rounds after rebase.

@Astro-Han

Copy link
Copy Markdown
Contributor

Thanks — the split here looks right. Ordinary command semantics now use the existing in-process dependency seam, while piped stdin, SIGINT, and the sandbox fail-closed boundary remain real subprocess tests. Sharing one scenario fake across both routes also avoids creating parallel behavior.

I did not find a code or test-design issue that requires changing the approach. The only remaining step is resolving the current conflict with main.

When rebasing, please keep the deletion from #2478 of the low-value “names the mode the same way...” help-copy test. This branch migrated that old test in process, but it should not be reintroduced during conflict resolution.

After the rebase, please rerun CI on the new merge head. Assuming it stays green, this should be ready to merge. Thanks!

@Astro-Han Astro-Han 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.

Thanks — this is a thoughtful test refactor. Moving ordinary command semantics onto the existing in-process seam while retaining real subprocess coverage for stdin, SIGINT, and fail-closed behavior is the right split. Sharing one fake between both routes also avoids parallel test implementations.

One small non-blocking suggestion: please keep an assertion that a successful real subprocess produces no unexpected stderr output. The injected in-process stderr cannot observe code that writes directly to process.stderr, but this can be covered by adding the existing filtered-empty assertion to one retained successful subprocess test without adding another spawn.

The current head conflicts with main, so it still needs a rebase. During resolution, please preserve the current run-command-core / MakaRunOutcome architecture, the newer Session-name normalization test, and the deletion of the old help-copy test from #2478. After that, a green CI run should be sufficient.

Approved — thanks for the clear process-boundary reasoning and timing evidence!

@Astro-Han

Copy link
Copy Markdown
Contributor

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@Astro-Han

Copy link
Copy Markdown
Contributor

Hi — this PR conflicts with current main and cannot be merged as-is.

I tested a rebase onto current main locally (in a throwaway worktree — your branch was not touched). It stops on these files:

  • packages/cli/src/__tests__/run-command-fixture.ts
  • packages/cli/src/__tests__/run-command.test.ts

These are real source conflicts, so they need your judgement rather than a mechanical rebase — please rebase onto current main and resolve them yourself, then push. Once the branch is conflict-free and CI is green on the new head, I will pick it up for review.

git fetch upstream && git rebase upstream/main
# resolve, then
git push --force-with-lease

Thanks for the contribution — happy to help if any conflict is unclear.


AI-assisted maintenance note, not a review. It does not count as the required human review under CONTRIBUTING.md §Review.

@Astro-Han

Astro-Han commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

This branch is 739 commits behind main, and both files it restructures — run-command-fixture.ts and run-command.test.ts — have changed on main since. Because this PR effectively replaces those two files (splitting the fixture into run-command-fake.ts and rewriting 245 lines of cases), a mechanical rebase across that gap would overwrite the cases main added later.

Could you rebase onto current main, paying particular attention to keeping the cases main added to run-command.test.ts? Once CI is green I will pick it back up.

简体中文

这条落后 main 739 个提交,而且它重构的两个文件(run-command-fixture.tsrun-command.test.ts)main 上都已经改过。这个 PR 的做法是把 fixture 拆成 run-command-fake.ts 并重写 245 行用例,等于整体替换这两个文件——在这么大的落差下机械 rebase 会把 main 上后来加的用例覆盖掉。

麻烦在当前 main 上重新 rebase 一次,重点确认 run-command.test.ts 里 main 新增的用例都还在。rebase 完 CI 绿了我这边继续跟。

@Astro-Han

Copy link
Copy Markdown
Contributor

Closing this as superseded — the goal was reached on main through a different route, and the parent issue #2387 was closed on 2026-08-17.

main now extracts the run path into packages/cli/src/run-command-core.ts and exposes runMakaTextCliCore behind a MakaRunAdapter seam. run-command.test.ts is 150 lines and drives that adapter directly, with no subprocess and no fixture at all. run-command-fixture.ts — the file this PR splits — no longer exists, so there is nothing left here to rebase onto: reviving it would mean writing a different change, not replaying this one.

Thank you for the work, and sorry it did not land first. The measurement in your description was the useful part of it, and the outcome matches what you set out to get.

One thing from this PR did not survive the alternative, and may be worth a follow-up. You deliberately kept six tests on a real subprocess for contracts the seam cannot express:

  • piped non-TTY stdin (run -, implicit stdin prompt, positional + stdin context)
  • SIGINT delivery → exit 130 with empty stdout through a real pipe
  • the fail-closed sandbox boundary reaching a non-interactive run

I looked for equivalent coverage on current main and did not find it. stdinIsTTY and readStdin are exercised in process through the injected seam in runtime-host-run-command.test.ts and activation-command.test.ts, which covers the semantics but not the real pipe. I found no assertion anywhere in the repository that a SIGINT to the CLI yields exit 130. The remaining spawn in cli.test.ts is about profile and identity files, not the run path.

So the fast in-process route landed and the process-boundary guard did not. If you would like to open a small PR restoring just those contracts on top of today's run-command-core.ts, I think that is worth having, and I would be glad to review it.

简体中文

这个 PR 已被取代,我把它关掉。目标在 main 上通过另一条路线达成了,父 issue #2387 也已于 2026-08-17 关闭。

main 现在把运行路径抽到了 packages/cli/src/run-command-core.ts,通过 MakaRunAdapter 这个注入缝暴露 runMakaTextCliCorerun-command.test.ts 只有 150 行,直接驱动该 adapter,完全没有子进程、也没有 fixture。本 PR 要拆分的 run-command-fixture.tsmain 上已不存在,所以这里没有可以 rebase 的东西了——恢复它等于重写一个新改动,而不是重放这一个。

感谢你的工作,很抱歉它没能先落地。你描述里的那份测量是很有价值的部分,而最终结果和你想要的一致。

这个 PR 里有一样东西没有在替代方案中保留下来,可能值得做个后续。 你当时特意把六个测试留在真实子进程上,因为那几条契约是注入缝表达不了的:

  • 管道非 TTY stdin(run -、隐式 stdin prompt、位置参数 + stdin 上下文)
  • SIGINT 送达 → 退出码 130,且通过真实管道时 stdout 为空
  • 失败关闭的沙箱边界抵达非交互运行

我在当前 main 上找过等价覆盖,没有找到。stdinIsTTYreadStdinruntime-host-run-command.test.tsactivation-command.test.ts 里通过注入缝在进程内被覆盖,覆盖到了语义,但没有覆盖真实管道。整个仓库里我没有找到任何断言「向 CLI 发送 SIGINT 会得到退出码 130」。cli.test.ts 里剩下的 spawn 针对的是 profile 和 identity 文件,不是运行路径。

也就是说,进程内的提速落地了,进程边界的守卫没有。如果你愿意基于今天的 run-command-core.ts 开一个小 PR,只把那几条契约补回来,我认为这是值得有的,我很乐意 review。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Opened #3710 restoring exactly the six process-boundary contracts enumerated above, on top of today's run-command-core.ts — a minimal subprocess fixture over runMakaTextCliCore with default environment deps, none of the in-process duplication 802855c removed. Assertions are unchanged from the pre-deletion suite; both SIGINT tests carry bounded guards so a regression fails instead of hanging.

UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 24, 2026
Restores the six contracts apache#2476 kept on a real subprocess and 802855c
dropped with the process fixture: piped non-TTY stdin (run -, implicit
stdin prompt, positional plus stdin context), SIGINT delivery observed
as exit 130 with empty stdout, and the fail-closed sandbox boundary
reaching a non-interactive run. The injected MakaRunDeps seam covers
these semantics in process but not the boundary itself.

The new fixture is a minimal subprocess entry over runMakaTextCliCore
with default environment deps and four scripted scenarios — none of the
in-process duplication the removal targeted.

Generated-by: Claude Code
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.

3 participants