Skip to content

Async Exec Refactor (Cycle 0024) - #11

Merged
flyingrobots merged 4 commits into
mainfrom
cycles/0024-async-exec-refactor
Apr 7, 2026
Merged

flyingrobots merged 4 commits into
mainfrom
cycles/0024-async-exec-refactor

Conversation

@flyingrobots

Copy link
Copy Markdown
Owner

Summary

Replaces blocking execSync with async promisify(exec) in Workspace.execCommand. The async cascade flows through captureWitness → closeCycle → CLI/MCP callers. Clears the bad-code lane.

  • execCommand returns Promise<string>, supports timeoutMs option
  • captureWitness and closeCycle are now async
  • CLI and MCP callers updated with await
  • Ship-sync and witness tests updated for async closeCycle
  • 5 new tests in tests/exec.test.ts

Test plan

  • 118 tests pass (113 prior + 5 new)
  • Build clean
  • Cycle closed with retro and witness

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.
@coderabbitai

coderabbitai Bot commented Apr 7, 2026 •

Copy link
Copy Markdown

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added design documentation and completion retro for command execution refactor.
  • New Features

    • Added timeout support for command execution via configurable timeoutMs parameter.
    • Event loop no longer blocked during witness capture.
  • Tests

    • Added comprehensive test coverage for async command execution, witness capture, and timeout behavior.

Walkthrough

This pull request refactors the core Workspace API from synchronous to asynchronous execution. execCommand, captureWitness, and closeCycle methods are converted to async, replacing execSync with promisified exec and adding timeout support via a timeoutMs option. CLI and MCP handlers are updated to await these async operations, and comprehensive test coverage validates the new async behavior and timeout semantics.

Changes

Cohort / File(s) Summary
Documentation & Design Artifacts
docs/design/0024-async-exec-refactor/async-exec-refactor.md, docs/method/retro/0024-async-exec-refactor/async-exec-refactor.md, docs/method/retro/0024-async-exec-refactor/witness/verification.md, docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md
Moves async refactor design from backlog to formal design document; adds retro documentation with witness verification output and drift analysis; removes redundant backlog entry.
Core Workspace API
src/index.ts
Converts execCommand, captureWitness, and closeCycle to async methods; replaces execSync with promisified exec; adds timeoutMs option with timeout rejection logic; propagates Promise returns through the API.
CLI & MCP Integration
src/cli.ts, src/mcp.ts
Adds await to closeCycle() and captureWitness() invocations in command handlers and MCP tool definitions to consume async results before continuing execution.
Test Suite Expansion
tests/exec.test.ts
New test file validating async execution contract: Promise returns, command output capture, METHOD_TEST mock behavior, timeout rejection, and temporary workspace cleanup.
Existing Test Updates
tests/witness.test.ts, tests/ship-sync.test.ts
Updates test callbacks to async and adds await keywords for captureWitness() and closeCycle() invocations to properly handle Promise results.

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

  • Automated Witness Capture (Cycle 0020) #6: Directly related—both modify execCommand, captureWitness, closeCycle and their call sites; this PR converts the synchronous witness-capture implementation into an async, Promise-based version.
  • Shape release workflow and user migration docs #5: Both PRs refactor the same Workspace API surface (src/index.ts, src/cli.ts, src/mcp.ts) and associated test infrastructure, with async conversion building on earlier refactoring groundwork.

Poem

🚀 From sync's iron chains we break free,
Event loops breathe async jubilee!
Promises bloom where execSync once stood,
Witness capture—finally—works as it should. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the main change: converting synchronous exec to async in Workspace.execCommand as part of Cycle 0024.
Description check ✅ Passed The description comprehensively relates to the changeset, detailing the async cascade from execCommand through captureWitness and closeCycle, API changes, test coverage, and verification results.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cycles/0024-async-exec-refactor

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 and usage tips.

@flyingrobots
flyingrobots merged commit e8b4a70 into main Apr 7, 2026
2 of 3 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 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".

Comment thread src/index.ts
Comment on lines +540 to +541
if (error.killed || error.signal === 'SIGTERM') {
throw new MethodError(`Command timed out: ${command}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread src/index.ts
encoding: 'utf8',
timeout: options?.timeoutMs,
});
return stdout + stderr;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@coderabbitai coderabbitai Bot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between a283233 and 92fabe8.

📒 Files selected for processing (10)
  • docs/design/0024-async-exec-refactor/async-exec-refactor.md
  • docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md
  • docs/method/retro/0024-async-exec-refactor/async-exec-refactor.md
  • docs/method/retro/0024-async-exec-refactor/witness/verification.md
  • src/cli.ts
  • src/index.ts
  • src/mcp.ts
  • tests/exec.test.ts
  • tests/ship-sync.test.ts
  • tests/witness.test.ts
💤 Files with no reviewable changes (1)
  • docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md

Comment on lines +3 to +4
outcome: hill-met
drift_check: yes

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment thread tests/exec.test.ts
Comment on lines +89 to +92
// 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -20

Repository: 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 -10

Repository: flyingrobots/method

Length of output: 45


🏁 Script executed:

# Find the implementation of execCommand
rg -n "execCommand" --type ts -B2 -A10 | head -80

Repository: flyingrobots/method

Length of output: 3591


🏁 Script executed:

# Read the full test file to understand context
wc -l tests/exec.test.ts

Repository: flyingrobots/method

Length of output: 84


🏁 Script executed:

# Get the test file content around line 89-92
sed -n '75,105p' tests/exec.test.ts

Repository: flyingrobots/method

Length of output: 749


🏁 Script executed:

# Check if sleep is used elsewhere in tests
rg "sleep" --type ts --type js

Repository: 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.

Suggested change
// 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.

@flyingrobots
flyingrobots deleted the cycles/0024-async-exec-refactor branch April 7, 2026 07:08
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