Repository navigation
Async Exec Refactor (Cycle 0024) - #11
Conversation
Replace blocking execSync with async exec in Workspace.execCommand. Clears the bad-code lane.
New tests/exec.test.ts with 5 tests: - execCommand returns Promise, captureWitness async, closeCycle async, METHOD_TEST mock preserved, timeout cancellation. 4 tests fail: methods are still synchronous.
- execCommand now returns Promise<string> using promisified child_process.exec - captureWitness and closeCycle are async, awaited by CLI and MCP callers - Supports configurable timeout (timeoutMs option) with clear error on kill - METHOD_TEST mock path preserved with identical output format - Updated ship-sync and witness tests for async closeCycle - 5 new dedicated tests in tests/exec.test.ts Closes the bad-code lane. Event loop no longer blocks during witness capture.
Hill met. execSync replaced with async exec. Timeout support added. Bad-code lane cleared. 118 tests pass. No drift. No new debt.
Summary by CodeRabbitRelease Notes
WalkthroughThis pull request refactors the core Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Justification: Multiple heterogeneous changes across core API signatures, integration points (CLI/MCP), and test coverage. While the async/await pattern is consistent, reviewers must validate: (1) correct Promise propagation across all call sites, (2) timeout exception handling semantics, (3) integration correctness in CLI and MCP handlers, (4) test coverage adequacy for the new async contract, and (5) that no synchronous call sites remain after the refactor. The changes span 8 files with varying complexity per file. Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92fabe8229
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (error.killed || error.signal === 'SIGTERM') { | ||
| throw new MethodError(`Command timed out: ${command}`); |
There was a problem hiding this comment.
Restrict timeout errors to timed executions
execCommand now throws Command timed out for any child process that exits with SIGTERM, even when no timeoutMs was provided. In practice, commands can terminate with SIGTERM for reasons unrelated to a timeout (self-termination, external signal, CI teardown), and this will now abort captureWitness/closeCycle with a misleading timeout error instead of returning command output as before. The timeout mapping should be gated on an explicit timeout path (for example, only when options?.timeoutMs is set).
Useful? React with 👍 / 👎.
| encoding: 'utf8', | ||
| timeout: options?.timeoutMs, | ||
| }); | ||
| return stdout + stderr; |
There was a problem hiding this comment.
Preserve success-path stdout behavior
The success path now returns stdout + stderr, but the previous implementation returned only stdout on successful commands. This changes witness content whenever commands emit warnings to stderr with exit code 0 (common with toolchain warnings), so verification.md output format is no longer stable across environments. If backward-compatible output is expected, keep stderr out of the success return value.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/method/retro/0024-async-exec-refactor/async-exec-refactor.md`:
- Around line 3-4: The retro metadata keys outcome: hill-met and drift_check:
yes are inconsistent with the witness verification playback-question/test titles
(the human playback and agent questions) which still show exact-match misses;
update the playback-question text/test titles in the witness verification
document to match the intended successful phrasing, or alternatively downgrade
the metadata (e.g., set outcome to a non-hill-met value and/or drift_check: no)
until the drift scan passes so the metadata and evidence agree. Ensure you
change the playback-question strings and corresponding test titles (and/or the
outcome/drift_check values) so they are consistent.
In `@tests/exec.test.ts`:
- Around line 89-92: The test uses the shell-only command 'sleep 30' which fails
on Windows; update the test in exec.test.ts to run a portable Node-based sleeper
instead: call workspace.execCommand with a command that invokes the current Node
runtime (use process.execPath) to run a short script that blocks via setTimeout
for ~30s so the execCommand timeout path is exercised; keep the same timeoutMs
(200) and the same expect(...).rejects.toThrow check to validate the
timeout/killed behavior of execCommand.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 66af8756-7c31-4d6c-b1b7-063c6b2fcfcf
📒 Files selected for processing (10)
docs/design/0024-async-exec-refactor/async-exec-refactor.mddocs/method/backlog/bad-code/PROCESS_async-exec-refactor.mddocs/method/retro/0024-async-exec-refactor/async-exec-refactor.mddocs/method/retro/0024-async-exec-refactor/witness/verification.mdsrc/cli.tssrc/index.tssrc/mcp.tstests/exec.test.tstests/ship-sync.test.tstests/witness.test.ts
💤 Files with no reviewable changes (1)
- docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md
| outcome: hill-met | ||
| drift_check: yes |
There was a problem hiding this comment.
The retro metadata is ahead of the evidence.
The linked witness still reports exact-match misses for the human playback question and most agent questions in docs/method/retro/0024-async-exec-refactor/witness/verification.md, Lines 29-44. Marking this retro as hill-met and drift_check: yes makes the process artifacts disagree; align the playback-question text/test titles or downgrade the outcome until the drift scan is green.
Also applies to: 10-11
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/method/retro/0024-async-exec-refactor/async-exec-refactor.md` around
lines 3 - 4, The retro metadata keys outcome: hill-met and drift_check: yes are
inconsistent with the witness verification playback-question/test titles (the
human playback and agent questions) which still show exact-match misses; update
the playback-question text/test titles in the witness verification document to
match the intended successful phrasing, or alternatively downgrade the metadata
(e.g., set outcome to a non-hill-met value and/or drift_check: no) until the
drift scan passes so the metadata and evidence agree. Ensure you change the
playback-question strings and corresponding test titles (and/or the
outcome/drift_check values) so they are consistent.
| // A command that would hang should be killed by the timeout | ||
| await expect( | ||
| workspace.execCommand('sleep 30', { timeoutMs: 200 }), | ||
| ).rejects.toThrow(/timed out|killed|abort/iu); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== CI workflow OS matrix / runs-on =="
if [ -d .github/workflows ]; then
rg -n -C2 'runs-on|matrix|os' .github/workflows || true
else
echo "No .github/workflows directory found."
fi
echo
echo "Expected verification outcome:"
echo "- If any Windows/macOS runner exists, 'sleep 30' is a portability risk."Repository: flyingrobots/method
Length of output: 585
🏁 Script executed:
# Check project configuration and platform scope
cat README.md 2>/dev/null | head -50 || echo "No README found"Repository: flyingrobots/method
Length of output: 1824
🏁 Script executed:
# Check package.json for platform info
cat package.json 2>/dev/null | grep -A5 -B5 '"engines"\|"os"\|"platform"' || echo "No platform specs in package.json"Repository: flyingrobots/method
Length of output: 211
🏁 Script executed:
# Find and examine the execCommand implementation
fd -t f "exec" | grep -E "\.(ts|js)$" | head -20Repository: flyingrobots/method
Length of output: 81
🏁 Script executed:
# Check if there are any platform-specific guards or Windows-related code
rg -i "windows|win32|platform|posix" --type ts --type js -l | head -10Repository: flyingrobots/method
Length of output: 45
🏁 Script executed:
# Find the implementation of execCommand
rg -n "execCommand" --type ts -B2 -A10 | head -80Repository: flyingrobots/method
Length of output: 3591
🏁 Script executed:
# Read the full test file to understand context
wc -l tests/exec.test.tsRepository: flyingrobots/method
Length of output: 84
🏁 Script executed:
# Get the test file content around line 89-92
sed -n '75,105p' tests/exec.test.tsRepository: flyingrobots/method
Length of output: 749
🏁 Script executed:
# Check if sleep is used elsewhere in tests
rg "sleep" --type ts --type jsRepository: flyingrobots/method
Length of output: 144
Replace sleep 30 with Node-based alternative for Windows compatibility.
The execCommand method uses child_process.exec(), which invokes the platform's native shell. On Windows (cmd.exe), the sleep command does not exist, causing the timeout test to fail before the timeout mechanism can even be exercised. The project targets Node.js 22+ across all platforms with no OS restrictions, so this test must be platform-agnostic.
Portable fix
- await expect(
- workspace.execCommand('sleep 30', { timeoutMs: 200 }),
- ).rejects.toThrow(/timed out|killed|abort/iu);
+ await expect(
+ workspace.execCommand('node -e "setTimeout(function(){}, 30000)"', { timeoutMs: 200 }),
+ ).rejects.toThrow(/timed out|killed|abort/iu);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // A command that would hang should be killed by the timeout | |
| await expect( | |
| workspace.execCommand('sleep 30', { timeoutMs: 200 }), | |
| ).rejects.toThrow(/timed out|killed|abort/iu); | |
| // A command that would hang should be killed by the timeout | |
| await expect( | |
| workspace.execCommand('node -e "setTimeout(function(){}, 30000)"', { timeoutMs: 200 }), | |
| ).rejects.toThrow(/timed out|killed|abort/iu); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/exec.test.ts` around lines 89 - 92, The test uses the shell-only
command 'sleep 30' which fails on Windows; update the test in exec.test.ts to
run a portable Node-based sleeper instead: call workspace.execCommand with a
command that invokes the current Node runtime (use process.execPath) to run a
short script that blocks via setTimeout for ~30s so the execCommand timeout path
is exercised; keep the same timeoutMs (200) and the same
expect(...).rejects.toThrow check to validate the timeout/killed behavior of
execCommand.
Summary
Replaces blocking
execSyncwith asyncpromisify(exec)inWorkspace.execCommand. The async cascade flows throughcaptureWitness→closeCycle→ CLI/MCP callers. Clears the bad-code lane.execCommandreturnsPromise<string>, supportstimeoutMsoptioncaptureWitnessandcloseCycleare now asyncawaitcloseCycletests/exec.test.tsTest plan