Skip to content

Port six sandcastle features to Eden#2

Merged
nicholasadamou merged 3 commits into
mainfrom
claude/port-sandcastle-features-KL2Vy
May 10, 2026
Merged

Port six sandcastle features to Eden#2
nicholasadamou merged 3 commits into
mainfrom
claude/port-sandcastle-features-KL2Vy

Conversation

@nicholasadamou
Copy link
Copy Markdown
Member

@nicholasadamou nicholasadamou commented May 10, 2026

Audit of sandcastle v0.5.0–v0.5.10 against Eden surfaced five gaps; this PR closes them and adds a recovery-message helper as a sixth port. Two of the changes are user-visible behavior shifts — see CHANGELOG.md.

Changes

1. Shell-injection guard in prompt_args

render_prompt now substitutes built-ins, expands !`cmd` shell blocks against the raw template only, and substitutes user args last. An arg value containing !`...` text is treated as inert data instead of triggering subprocess execution. Mirrors sandcastle's 6bc4d74.

2. Inline prompts pass through verbatim

prompt="..." strings now skip {{KEY}} substitution, !`cmd` shell expansion, and {{SOURCE_BRANCH}} / {{TARGET_BRANCH}} injection. Only prompt_file=... goes through the render pipeline. Mirrors sandcastle's 4032e64.

3. AgentError with parsed-stdout details

When an agent EOFs with a non-zero exit and no completion signal, the loop raises a typed AgentError immediately instead of waiting for idle_timeout. parse_stdout_error extracts text from Codex / Pi {"type":"error",...} events and OpenCode's result text — these emit on stdout, not stderr.

4. CopyToWorktreeError + Timeouts.copy_to_worktree

Isolated provider's worktree clone now bounds at 60s by default and raises a typed error on timeout or copy failure. New isolated.provider(copy_timeout=...) knob (pass None to disable).

5. Commit-aware finalize log

Replaces [eden] finalized: applied=True files=N bytes=B with [eden] no changes to sync / [eden] syncing N file(s) to host (B bytes) / [eden] sync incomplete: ....

6. Recovery hint stream event (port of sandcastle's RecoveryMessage)

format_agent_error_recovery emits a copy-pastable hint via the run sink before raising AgentError: agent name, exit code, parsed error, branch, worktree, log path, and cd / git status / git diff / eden clean next-step commands.

Behavior changes (worth flagging in release notes)

  • Inline prompt="..." no longer expands {{SOURCE_BRANCH}}. Move templated prompts to a file or build the string in Python before passing it inline.
  • Agents that exit non-zero without a completion signal now fail fast with AgentError instead of waiting for idle_timeout to fire.

Test plan

  • Full pytest suite: 668 passed, 13 skipped (docker/podman daemon unavailable locally)
  • Ruff format & lint clean
  • mypy --strict eden tests clean (191 source files)
  • New unit tests: tests/unit/test_agent_errors.py, tests/unit/test_recovery_message.py
  • New e2e tests: tests/e2e/test_agent_error.py covering parsed-stdout, stderr-fallback, clean-exit-with-completion, and zero-exit-without-completion paths
  • Updated existing e2e finalize-line assertions to match the new [eden] syncing ... wording
  • CI green on all matrix entries (ubuntu/macos/windows × py3.11/3.12/3.13)

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added recovery hints and guidance displayed when agents encounter failures
    • Added configurable timeout control for copy operations with per-call overrides
  • Changed

    • Agent now fails immediately on non-zero exit without waiting for completion signal
    • Enhanced error logging with detailed recovery suggestions and instructions
  • Documentation

    • Updated changelog documenting new error types and behavioral improvements

Review Change Stack

- Adds ``eden/orchestrator/_recovery.format_agent_error_recovery`` and
  emits its output as a stream event before the loop raises
  ``AgentError``. Users now see a copy-pastable recovery hint
  (worktree, log, branch, next-step commands) in the run log even when
  the calling code swallows the exception.
- Fixes a latent bug where the ``AgentError`` raise path read
  ``runner.exit_code()`` AFTER the ``with`` block had already invoked
  ``terminate()`` and nulled the process handle. Captures the exit
  code and stderr inside the ``with`` block instead.
- Adds e2e coverage in ``tests/e2e/test_agent_error.py`` driving the
  full loop with a Codex-shape stdout error event, an empty-stdout
  agent that writes only stderr, and clean-exit baselines.
- New ``CHANGELOG.md`` documenting all changes since the last release,
  including the inline-prompt-literal and prompt-arg shell-injection
  behavior shifts.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

Warning

Rate limit exceeded

@nicholasadamou has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 45 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57963f88-a1ed-4562-9e99-738a3247bdc5

📥 Commits

Reviewing files that changed from the base of the PR and between bbe07e1 and 6830dba.

📒 Files selected for processing (2)
  • eden/orchestrator/_recovery.py
  • tests/unit/test_recovery_message.py
📝 Walkthrough

Walkthrough

This PR implements a new error recovery messaging system. A new public function, format_agent_error_recovery(), generates copy-pastable recovery commands for agent failures. The orchestrator loop captures agent exit code and stderr, constructs AgentError when the agent exits non-zero without a completion signal, generates recovery text, emits it as a stream event, and raises the error. Unit and E2E tests validate the formatter logic and orchestrator integration. The changelog documents new features and behavioral changes.

Changes

Agent Error Recovery Implementation

Layer / File(s) Summary
Recovery Message Formatter API
eden/orchestrator/_recovery.py
New format_agent_error_recovery() function generates multi-line recovery messages from AgentError fields, branch, worktree path, and optional log path. Conditionally includes log guidance and appends "Next steps" shell commands.
Orchestrator Error Handling
eden/orchestrator/_loop.py
Orchestrator loop captures agent exit code and stderr after runner context closes. On no-completion-signal error, constructs AgentError, generates recovery text via format_agent_error_recovery, emits recovery as StreamEvent, and raises the error.
Unit Tests for Recovery Formatter
tests/unit/test_recovery_message.py
Six unit tests validate format_agent_error_recovery output: parsed error inclusion, stderr fallback, empty output handling, log path omission, next steps commands, and agent name/exit code inclusion.
E2E Tests for Error Integration
tests/e2e/test_agent_error.py
Four E2E tests verify orchestrator behavior: non-zero exit with Codex error event raises AgentError with recovery hint emission, stderr fallback when no stdout error, clean exit with completion signal succeeds, zero exit without completion preserves completion_signal=None semantics.
Changelog Documentation
CHANGELOG.md
Documents new typed errors, recovery message helper, copy-to-worktree timeout controls, and behavioral changes: verbatim prompt handling, agent failure fast-path, and commit-aware final log format.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 When agents falter, recovery calls,
A formatter whispers through sandcastle halls,
With worktree paths and next-step commands,
The loop now catches where failure stands,
Copy, paste, and heal the sands! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Port six sandcastle features to Eden' is vague and doesn't clearly communicate the main change; it references a source (sandcastle) and mentions quantity but lacks specifics about what was actually changed. Consider a more specific title such as 'Add AgentError with recovery hints and timeout handling' or 'Implement error recovery and timeout features from sandcastle' to better convey the primary changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/port-sandcastle-features-KL2Vy

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@eden/orchestrator/_recovery.py`:
- Around line 48-53: The shell commands appended to lines use unquoted variables
(worktree_path, log_path, branch) which will break on spaces/special chars; wrap
these values with a proper shell-quoting function (e.g., import shlex and use
shlex.quote(value)) when building the strings passed to lines.append for the cd,
less and git diff commands (and any other places that interpolate those
variables), leaving the if log_path is not None guard intact so you only quote
when present.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac58e088-a31d-460a-9593-6e1741a607b1

📥 Commits

Reviewing files that changed from the base of the PR and between 0937eb6 and bbe07e1.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • eden/orchestrator/_loop.py
  • eden/orchestrator/_recovery.py
  • tests/e2e/test_agent_error.py
  • tests/unit/test_recovery_message.py

Comment thread eden/orchestrator/_recovery.py Outdated
Test User added 2 commits May 10, 2026 17:15
CodeRabbit caught that the ``cd`` / ``less`` / ``git diff`` lines in the
recovery message interpolate ``worktree_path`` / ``log_path`` / ``branch``
without quoting — a worktree under ``/Users/Foo Bar/...`` (a real macOS
case) would break the copy-paste. Wrap each interpolated value with
``shlex.quote``.

Also fixes Windows CI failures: the unit tests asserted ``"/tmp/eden/wt"``
appeared in the formatter output, but on Windows ``Path("/tmp/eden/wt")``
stringifies as ``\\tmp\\eden\\wt``. Switched the tests to ``tmp_path``
fixtures and ``str(path)`` so the assertion matches the OS-native
representation. Added two new tests pinning the shell-quoting behavior
for paths with spaces and branches with special characters.
The two tests added in 14d53b6 (``test_quotes_path_with_spaces`` and
``test_quotes_branch_with_special_chars``) constructed ``Path("/Users/Foo
Bar/...")`` and asserted against POSIX-style literals. On Windows that
``Path`` stringifies to ``\\Users\\Foo Bar\\...``, so ``shlex.quote`` and
the assertion didn't match. Switch to ``tmp_path / "Foo Bar" / ...`` and
compare against ``shlex.quote(str(wt))`` so both halves use the same
OS-native repr.
@nicholasadamou nicholasadamou merged commit c05b8cf into main May 10, 2026
10 checks passed
@nicholasadamou nicholasadamou deleted the claude/port-sandcastle-features-KL2Vy branch May 10, 2026 17:31
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