Skip to content

feat(devin): establish session policy foundation - #1

Closed
OnlineChef wants to merge 0 commit into
mainfrom
devin-rust-core
Closed

feat(devin): establish session policy foundation#1
OnlineChef wants to merge 0 commit into
mainfrom
devin-rust-core

Conversation

@OnlineChef

@OnlineChef OnlineChef commented Jul 31, 2026

Copy link
Copy Markdown

Summary

  • pin the 28-tool live Devin CLI contract from four matching local transcript fixtures
  • add session-scoped agent/permission modes with versioned session persistence
  • enforce central allow/ask/deny/sandbox policy decisions before approvals, hooks, and execution
  • add workspace, traversal, symlink, autonomous fail-closed, and secret-minimizing audit coverage

Test plan

  • GitHub Actions formatting and lint gates pass
  • Devin contract and session-state tests pass
  • Default all-target test suite and release build pass

Definition of Done Evidence

  • Unit evidence linked
  • E2E evidence linked
  • Extension evidence linked
  • Failing paths include artifact links and repro commands

Evidence:

Reproduction Commands

  • Unit: cargo test --all-targets
  • E2E: ./scripts/e2e/run_all.sh --profile ci
  • Extension: cargo test --all-targets --features ext-conformance
  • Focused: cargo test --test devin_contract --test devin_session_state
  • Release: cargo build --release

Rust verification is CI-only for this branch. The pristine upstream --all-features lane has a pre-existing WASM-host Future + Send failure documented in the parity progress file.

Made with Cursor


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.

Greptile Summary

This PR adds Devin-compatible session persistence, policy decisions, scope grants, audit records, and an optional policy gate for native agent tool execution.

The policy gate currently refuses autonomous process and network operations rather than executing them in a sandbox, permits Bypass-mode process and network operations without an effective containment boundary, and records audit outcomes before later approval or execution results are known. These behaviors make the permission model and its audit trail unreliable.

T-Rex validation blocked

The focused Rust tests could not complete because the required asupersync compilation exceeded the command-runner timeout, and one run was terminated while compiling. The affected test commands produced no completed runtime result, so no execution proof is reported for these findings. Configure VMs

Confidence Score: 2/5

Security Review

Bypass mode authorizes process and network tools even when the session has no applicable scope grants, and the execution path does not apply a later sandbox or scope-containment boundary. This can allow unrestricted local process or network effects despite the policy contract promising enforced scopes. This must be fixed before merge.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex analyzed the general-contract validation proof and identified a blocker due to a build execution timeout during the asupersync dependency graph compilation.
  • T-Rex noted that cargo test attempts for the autonomous_sandbox_policy_integration test repeatedly hit the Greptile command timeout while CPU usage stayed around 96%.
  • T-Rex recorded that the Greptile harness greptile-review_multibash returned MCP error -32001: Request timed out, ending the focused run.
  • T-Rex documented that target command processes remained running and their output capture files were empty, indicating no test results were produced in the focused run.
  • T-Rex uploaded the Rust build attempt artifact to help reviewers inspect the build and timeout conditions.

View all artifacts

T-Rex Ran code and verified through T-Rex

Fix All in Cursor Fix All in Codex Fix All in Claude Code Fix All in Conductor

Prompt To Fix All With AI
### Issue 1
src/agent.rs:2992-2995
**Sandbox actions are always denied**

When an active autonomous session invokes a process or network tool, the policy returns `Sandbox`, but this branch converts that action directly into a denied output, causing every operation intended for sandbox execution to fail.

### Issue 2
src/devin/policy.rs:302-304
**Bypass skips process containment**

When a Bypass session invokes `bash` or another process or network tool, path validation returns an empty successful scope and this branch authorizes execution without another containment boundary, allowing unrestricted effects despite Bypass being documented as limited to enforced scopes.

**How this was verified:** The `Allow` path proceeds from policy evaluation to the underlying tool without another scope or sandbox check.

### Issue 3
src/devin/policy.rs:163-169
**Audit records finalize before execution**

When approval, extension hooks, or tool execution later deny or fail a request, `audit_decision` has already set `ended_at` and recorded only the policy-level `Allowed` or `Pending` status, causing the audit history to disagree permanently with the actual outcome.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(devin): establish session policy fo..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

@warestack

warestack Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warestack Governance Checks

Warestack is installed for this GitHub account, but this repository is not ready to run governance checks yet.

Warestack lets your team define, enforce, and audit engineering standards directly on GitHub pull requests without leaving your workflow.

Get started in 2 minutes:

  1. Finish Warestack setup to activate governance for OnlineChefGroep/pi_agent_rust
  2. Configure rules for pull request quality, CI requirements, commit conventions, and more
  3. Receive automated feedback on every pull request

This message is posted once per new pull request while this repository is not configured.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@OnlineChef, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d3d42cad-19ab-4d16-bd42-33dc53d3ed19

📥 Commits

Reviewing files that changed from the base of the PR and between efa0861 and 9edee91.

📒 Files selected for processing (8)
  • .github/workflows/bench.yml
  • docs/TEST_COVERAGE_MATRIX.md
  • src/devin/audit.rs
  • src/devin/mod.rs
  • src/devin/policy.rs
  • src/devin/process.rs
  • src/devin/process_tools.rs
  • tests/devin_process_supervisor.rs
📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added configurable tool permissions with approval, denial, sandbox, and scoped-access controls.
    • Added secure session-state persistence and supervised process execution.
    • Added foreground/background commands, interactive input, output retrieval, cancellation, timeouts, and process termination.
    • Added audit records with sensitive argument and error redaction.
  • Bug Fixes
    • Improved protection against unsafe paths, traversal, and unsandboxed operations.
    • Corrected test discovery for feature-gated targets and colorized CI logs.
  • Documentation
    • Added guidance on compatibility, supported capabilities, limitations, and verification.

Walkthrough

The PR adds Devin session state, policy enforcement, audit logging, supervised process tools, session persistence, contract validation, CI coverage, feature-aware test selection, parity parsing, fuzz updates, and supporting documentation.

Changes

Devin runtime

Layer / File(s) Summary
Session state and persistence
src/devin/state.rs, src/session.rs, tests/devin_session_state.rs
Adds Devin modes, sandbox state, scope grants, shared state, custom session persistence, and branch-aware retrieval.
Policy evaluation and audit lifecycle
src/devin/policy.rs, src/devin/audit.rs
Adds tool classification, mode and permission rules, path and sandbox checks, bounded audit records, salted argument hashing, and credential redaction.
Agent policy gate
src/agent.rs
Adds optional session policy installation and applies policy decisions before approval and execution.
Process supervision and tool adapters
src/devin/process.rs, src/devin/process_tools.rs, src/tools.rs, tests/devin_process_supervisor.rs
Adds five supervised process tools with streaming output, stdin, timeouts, cancellation, process-group termination, detachment, artifacts, policy checks, and audit completion.
Public API and contract validation
src/devin/mod.rs, src/lib.rs, tests/devin_contract.rs, tests/fixtures/devin_cli/*, tests/suite_classification.toml
Exposes Devin APIs and validates pinned tool metadata, schemas, provenance, registry coverage, and test classification.

Verification and repository support

Layer / File(s) Summary
CI and test selection
.github/workflows/devin-ci.yml, .github/workflows/ci.yml, scripts/e2e/run_all.sh, .github/workflows/bench.yml, tests/security_budgets.rs
Adds Devin Rust CI, improves allowlist parsing, excludes feature-gated targets before sharding, updates benchmark setup, and adjusts a memory-test budget.
Parity evidence and path fuzzing
scripts/ci/generate_parity_evidence.py, fuzz/fuzz_targets/fuzz_tool_paths.rs
Adds ANSI log parsing and updates path assertions for lexical traversal cases.
Progress, coverage, and traceability
docs/devin-rust-progress.md, docs/TEST_COVERAGE_MATRIX.md, docs/traceability_matrix.json
Documents implementation status and updates coverage and traceability records.
Repository configuration
.github/workflows/conformance.yml, .gitignore
Sets the conformance skip step to the workspace root and anchors root-level core ignore rules.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant ToolPolicyEngine
  participant AuditLog
  participant ProcessTool
  participant ProcessSupervisor
  Agent->>ToolPolicyEngine: Evaluate tool request
  ToolPolicyEngine->>AuditLog: Record hashed decision
  ToolPolicyEngine-->>Agent: Return policy decision
  Agent->>ProcessTool: Invoke approved process tool
  ProcessTool->>ProcessSupervisor: Run or control process
  ProcessSupervisor-->>ProcessTool: Return output and process status
  ProcessTool->>AuditLog: Complete outcome record
  ProcessTool-->>Agent: Return structured result
Loading

Poem

A rabbit checks each tool with care,
Hashes secrets, keeps paths fair.
State moves safely branch to branch,
CI guards the Rusty ranch.
“Policy first!” the rabbit sings,
While Devin grows new safety springs.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the PR’s main change: establishing the Devin session policy foundation.
Description check ✅ Passed The description covers the summary, evidence, reproduction commands, and validation status, but it omits an explicit risk level.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin-rust-core

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.

Comment thread src/agent.rs Outdated
Comment on lines +2992 to +2995
"sandbox execution adapter is not configured; refusing unsandboxed execution",
)),
}
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Sandbox actions are always denied

When an active autonomous session invokes a process or network tool, the policy returns Sandbox, but this branch converts that action directly into a denied output, causing every operation intended for sandbox execution to fail.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent.rs
Line: 2992-2995

Comment:
**Sandbox actions are always denied**

When an active autonomous session invokes a process or network tool, the policy returns `Sandbox`, but this branch converts that action directly into a denied output, causing every operation intended for sandbox execution to fail.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Codex Fix in Claude Code Fix in Conductor

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deliberate fail-closed: PolicyAction::Sandbox is produced at src/devin/policy.rs:325 and consumed at src/agent.rs:2991, and no sandbox execution adapter exists anywhere in the repo, so refusing is strictly safer than running the call unsandboxed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That's a valid correction. The code path confirms it: PolicyAction::Sandbox only reaches agent.rs:2992 when SandboxStatus::Active was set but no execution adapter backs it — the sandbox != SandboxStatus::Active guard earlier at policy.rs:318 already converts a truly inactive sandbox into Deny. Refusing rather than falling through to unsandboxed execution is the correct fail-closed behavior for an unimplemented adapter.

The error message "sandbox execution adapter is not configured; refusing unsandboxed execution" makes the intent clear. No change needed here.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Comment thread src/devin/policy.rs Outdated
Comment on lines +302 to +304
PermissionMode::Bypass => (
PolicyAction::Allow,
"bypass mode allows calls inside enforced scopes".to_string(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Bypass skips process containment

When a Bypass session invokes bash or another process or network tool, path validation returns an empty successful scope and this branch authorizes execution without another containment boundary, allowing unrestricted effects despite Bypass being documented as limited to enforced scopes.

How this was verified: The Allow path proceeds from policy evaluation to the underlying tool without another scope or sandbox check.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/devin/policy.rs
Line: 302-304

Comment:
**Bypass skips process containment**

When a Bypass session invokes `bash` or another process or network tool, path validation returns an empty successful scope and this branch authorizes execution without another containment boundary, allowing unrestricted effects despite Bypass being documented as limited to enforced scopes.

**How this was verified:** The `Allow` path proceeds from policy evaluation to the underlying tool without another scope or sandbox check.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Codex Fix in Claude Code Fix in Conductor

Comment thread src/devin/policy.rs Outdated
Comment on lines +163 to +169
};
let now = Utc::now();
audit.push(AuditRecord {
call_id: request.call_id.clone(),
session_id: state.session_id.clone(),
parent_agent: state.parent_agent.clone(),
tool_name: request.tool_name.clone(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Audit records finalize before execution

When approval, extension hooks, or tool execution later deny or fail a request, audit_decision has already set ended_at and recorded only the policy-level Allowed or Pending status, causing the audit history to disagree permanently with the actual outcome.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/devin/policy.rs
Line: 163-169

Comment:
**Audit records finalize before execution**

When approval, extension hooks, or tool execution later deny or fail a request, `audit_decision` has already set `ended_at` and recorded only the policy-level `Allowed` or `Pending` status, causing the audit history to disagree permanently with the actual outcome.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Codex Fix in Claude Code Fix in Conductor

Copy link
Copy Markdown
Author

[code]smith (@codesmith-bot) Implement the next Devin runtime batch directly on the existing devin-rust-core branch, starting at current head 9e530763f54fbfe5aac7c0c755a87a1879ff2368. Do not create another branch or PR, and do not proceed to planning tools.

First inspect and reuse the existing native bash tool, cancellation structure, AgentCx, ToolUpdate streaming, output artifacts, ToolRegistry, and session cleanup. Build one session-owned native Rust process supervisor carrying the pinned Devin tools exec, shell_command, get_output, write_to_process, and kill_shell; do not create a second unrelated subprocess stack.

Required runtime behavior:

  • Registry entries: unique process ID, command, cwd, start time, status, exit code, process-group metadata, stdin handle, and bounded stdout/stderr buffers.
  • Foreground streams through the existing ToolUpdate route. Background returns quickly with a process ID and remains observable through get_output.
  • write_to_process safely writes stdin and clearly errors for unknown/closed processes.
  • kill_shell terminates the entire process group gracefully, then forcefully after a short grace period.
  • Session cancellation and stop clean up all owned processes. Detached behavior only when explicit and auditable.

Contracts/evidence:

  • Use tests/fixtures/devin_cli/tool_schema_manifest.json as the pinned contract source. Add full parameter-schema fixtures if missing; hashes alone are insufficient.
  • Treat the four current transcripts as pinned historical Devin 3000.2.17 evidence, not parity with installed 3000.3.22.
  • Search for a provenance-verifiable 3000.3.22 transcript before implementation; add it only when provenance and digest can be recorded. Otherwise document that none was accepted.

Policy integration:

  • Register through the existing ToolRegistry; every call must pass ToolPolicyEngine::evaluate first.
  • Plan technically denies exec, shell_command, write_to_process, and kill_shell.
  • Normal and Smart may ask for approval.
  • Bypass may auto-allow only while workspace/scope boundaries remain enforced; it must not bypass containment.
  • Autonomous process execution is allowed only with a genuinely active sandbox; without it, fail closed with Deny, not Ask/Allow.

Audit lifecycle:

  • Policy evaluation may create one pending record, but execution must update that same record by call_id to allowed, denied, succeeded, failed, cancelled, or timed out. Do not append a duplicate record for the same call.
  • Add timed-out status if absent. Store no raw arguments, secret-bearing commands, or unbounded stdout. Use salted argument hashes, redacted errors, and artifact references for large output.
  • Document that the per-AuditLog salt makes hashes comparable only inside that log, not cross-session fingerprints.

Tests required: short foreground process; background incremental output; stdin to interactive process; timeout; cancellation; full process-group kill; unknown process ID; closed stdin; output truncation; cleanup on session stop; Plan denial; Bypass cannot escape scope; Autonomous denial without sandbox.

Update docs/devin-rust-progress.md. Run/fix cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings, cargo test --test devin_contract --test devin_session_state plus the new supervisor tests, and cargo build --release. Commit the complete batch separately as feat(devin): add native process supervisor, push to devin-rust-core, and do not report completion until the actual devin-ci workflow and all its jobs are green.

@blacksmith-sh

blacksmith-sh Bot commented Jul 31, 2026

Copy link
Copy Markdown

Got it, I'm reviewing the PR comment and autofix results now.

@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: 15

🤖 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 @.github/workflows/devin-ci.yml:
- Around line 35-36: Pin both actions in .github/workflows/devin-ci.yml: at
lines 35-36, replace actions/checkout@v4 with commit SHA
11d5960a326750d5838078e36cf38b85af677262 and retain v4 as a comment; at lines
50-53, replace dtolnay/rust-toolchain@nightly with SHA
4fd1da8b0805d2d2e936788875a7d65dbd677dc2, retaining nightly only in the
toolchain input if required and preserving it as a reviewed-tag comment.

In `@scripts/e2e/run_all.sh`:
- Around line 486-495: Update the FEATURE_GATED_TARGETS manifest-parsing command
in scripts/e2e/run_all.sh to stop discarding parser errors and ignoring failures
via 2>/dev/null || true. Ensure the runner fails closed when TOML parsing cannot
run, either by requiring a Python runtime with tomllib support or using a
compatible parser, so feature-gated tests are not incorrectly treated as
ordinary targets.

In `@src/agent.rs`:
- Around line 3038-3045: Update the tool execution flow around
execute_tool_batch and record_outcome to close audit records for in-flight calls
discarded by abort handling. Track evaluated call_id values and, when the abort
branch returns without results, record AuditStatus::Cancelled with no error for
each call that produced no outcome, ensuring completed calls are not
overwritten.
- Around line 3046-3057: Bound the failed-tool output assembled in the
error_text flow before passing it to policy.record_outcome, limiting its length
while preserving the existing text-block filtering and joining behavior. Use the
existing AuditRecord::artifact_refs mechanism for large output rather than
retaining the full error string in the audit record.

In `@src/devin/audit.rs`:
- Around line 205-236: Add a unit test alongside the existing AuditLog tests
that creates an AuditLog with a small capacity, pushes capacity + 1 distinct
records through push, and verifies snapshot() contains exactly the newest
capacity records in insertion order, confirming the oldest record is evicted.
- Around line 96-110: Update AuditLog::complete to immediately reject an empty
call_id before searching records, and record the rejected completion as a miss
using the existing audit logging mechanism. Leave non-empty call_id matching and
completion behavior unchanged.
- Around line 175-203: Update redact_error to recognize credential key names,
including token, password, secret, api_key, apikey, and secret_access_key, when
followed by =, :, or JSON-style ": separators, case-insensitively. Redact the
credential value even when the key and value share one token, and set
redact_next for key tokens ending in = or : so separated values are replaced;
preserve the existing bearer, authorization, and sk- handling.

In `@src/devin/policy.rs`:
- Around line 277-291: Reorder the guard clauses in permission_decision: in
src/devin/policy.rs lines 277-291, evaluate the name == "request_scope" check
before the PermissionMode::Bypass early return so it always returns Ask; in
src/devin/policy.rs lines 280-285, evaluate the Autonomous plus non-Active
sandbox denial before the ToolCategory::Unknown early return so that case
returns Deny. Preserve the existing responses for all other inputs.
- Around line 507-536: Move the `outside` tempdir binding in
`traversal_and_symlink_escape_are_denied` into the existing `#[cfg(unix)]`
block, keeping it available for symlink creation while preventing
unused-variable warnings on non-Unix targets.
- Around line 406-411: Update the scope iteration in resolve_scoped_path around
canonicalize_allow_missing(&scope.root) so an unresolvable scope root is skipped
rather than propagated as an error. Continue checking subsequent scopes,
preserving the existing permit match and denial behavior when no valid scope
matches.

In `@src/devin/state.rs`:
- Around line 121-126: Document the precondition on the public scope_permits
method that path must already be resolved/canonicalized before calling it;
preserve its existing raw path comparison behavior and do not add
canonicalization unless required by the API contract.

In `@src/session.rs`:
- Around line 2877-2878: Update the custom-entry handling around the data
extraction in the session state lookup to return a session error when a matching
entry has data: None, rather than returning Ok(None). Preserve the existing
successful path for present data and the no-state result when no matching entry
exists.

In `@tests/devin_contract.rs`:
- Around line 29-31: Update local_devin_tool_surface_is_pinned() to resolve each
entry in manifest.sources against the committed Devin CLI transcript fixtures,
read their contents, and verify their hashes against the recorded values. Ensure
the referenced transcript files are present under the fixture directory so
changes to them cause the contract test to fail.

In `@tests/devin_session_state.rs`:
- Around line 30-31: Extend the restored-scope assertions in the test to verify
that restored.scopes[0].root equals external.path(), while preserving the
existing count and access checks.
- Around line 35-56: Update latest_devin_state_wins_on_current_branch to create
two sibling branches from a shared session entry, append distinct
DevinSessionState values to each branch, and navigate to each leaf before
asserting latest_devin_state returns that branch’s state. Use the existing
session branching/navigation APIs and preserve the branch-specific expectations.
🪄 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 Plus

Run ID: 95275383-eb2b-4174-97f0-0a7ffed1a2db

📥 Commits

Reviewing files that changed from the base of the PR and between 590d618 and 138741e.

📒 Files selected for processing (16)
  • .github/workflows/conformance.yml
  • .github/workflows/devin-ci.yml
  • .gitignore
  • docs/devin-rust-progress.md
  • scripts/e2e/run_all.sh
  • src/agent.rs
  • src/devin/audit.rs
  • src/devin/mod.rs
  • src/devin/policy.rs
  • src/devin/state.rs
  • src/lib.rs
  • src/session.rs
  • tests/devin_contract.rs
  • tests/devin_session_state.rs
  • tests/fixtures/devin_cli/tool_schema_manifest.json
  • tests/security_budgets.rs

Comment thread .github/workflows/devin-ci.yml Outdated
Comment thread scripts/e2e/run_all.sh Outdated
Comment thread src/agent.rs Outdated
Comment thread src/agent.rs Outdated
Comment thread src/devin/audit.rs Outdated
Comment thread src/devin/state.rs Outdated
Comment thread src/session.rs Outdated
Comment thread tests/devin_contract.rs Outdated
Comment on lines +29 to +31
let manifest: ToolSchemaManifest =
serde_json::from_str(include_str!("fixtures/devin_cli/tool_schema_manifest.json"))
.expect("valid Devin tool schema manifest");

@coderabbitai coderabbitai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for fixture in \
  wooded-guest.json \
  unmarred-barbecue.json \
  fork-class.json \
  steep-sidecar.json
do
  paths="$(fd -a -t f -g "$fixture" . || true)"
  if [ -z "$paths" ]; then
    echo "MISSING: $fixture"
    continue
  fi

  printf '%s\n' "$paths" | while IFS= read -r path; do
    sha256sum "$path"
  done
done

rg -n -C 3 --glob '*.rs' \
  'tool_schema_manifest|transcripts_compared|function\.parameters' \
  tests src

Repository: OnlineChefGroep/pi_agent_rust

Length of output: 2903


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== test file outline =="
ast-grep outline tests/devin_contract.rs || true
echo
echo "== devin_contract.rs relevant contents =="
cat -n tests/devin_contract.rs
echo
echo "== fixtures under tests/devin_contract.rs =="
fd -a -t f . tests/fixtures 2>/dev/null || true
echo
echo "== target fixture path if present =="
ls -l tests/fixtures/devin_cli 2>/dev/null || true
echo
echo "== git tracked filenames with devin/fixtures =="
git ls-files | rg 'devin|fixtures' || true

Repository: OnlineChefGroep/pi_agent_rust

Length of output: 50385


Hash the referenced transcript contents in the contract test.

local_devin_tool_surface_is_pinned() only deserializes fixture/devin_cli/tool_schema_manifest.json. manifest.sources only records named transcript hashes, but no fixture files with those names exist under tests/fixtures/devin_cli, so the check cannot catch changes to committed transcript sources. Include the transcript files or otherwise make the test read and verify the expected content.

🤖 Prompt for 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.

In `@tests/devin_contract.rs` around lines 29 - 31, Update
local_devin_tool_surface_is_pinned() to resolve each entry in manifest.sources
against the committed Devin CLI transcript fixtures, read their contents, and
verify their hashes against the recorded values. Ensure the referenced
transcript files are present under the fixture directory so changes to them
cause the contract test to fail.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The four named transcripts (wooded-guest.json etc.) are private local Devin CLI artifacts that are not committed anywhere in the repo and cannot be reconstructed here, so the manifest can only pin their digests as provenance; the executable contract is enforced by registered_process_tool_schemas_match_the_pinned_fixture against the full pinned parameter schemas.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Skipped: comment is from another GitHub bot.

Comment thread tests/devin_session_state.rs Outdated
Comment thread tests/devin_session_state.rs Outdated
@blacksmith-sh

blacksmith-sh Bot commented Jul 31, 2026

Copy link
Copy Markdown

OnlineChef

[code]smith (@codesmith-bot) Implement the next Devin runtime batch directly on the existing devin-rust-core branch, starting at current head 9e530763f54fbfe5aac7c0c755a87a1879ff2368. Do not create another bran...

Done. feat(devin): add native process supervisor is on devin-rust-core as 9d025b8b, and the real devin-ci workflow is fully green on that exact SHA — every step, including Build release binary.

What landed

A session-owned native process supervisor backing the five pinned Devin tools (exec, shell_command, get_output, write_to_process, kill_shell):

  • src/devin/process.rs — registry, bounded 256 KiB ring buffers with incremental cursors, spill to 0o600 artifact files past 64 KiB, foreground/background execution, ToolUpdate streaming, timeouts, ambient cancellation, and SIGTERM → 1.5 s grace → SIGKILL escalation across the whole process group.
  • src/devin/process_tools.rs — the five tool adapters with a policy gate and audit closure.
  • Reused the existing subprocess primitives in src/tools.rs rather than standing up a second stack; cleanup_temp_files now reaps process artifacts.

Merge note

While I was verifying, an autofix agent pushed 138741ef, which independently solved the same audit-closure problem with a different API. Rather than clobber it, I reconciled the two:

  • Kept my richer AuditLog (upsert-by-call_id, mark_allowed, TimedOut, artifact refs) and rebuilt upstream's record_outcome on top of complete_if_open, so denials are never reopened and a tool's specific TimedOut/Cancelled is never overwritten by the loop's generic result.
  • Removed my per-branch denial closures in agent.rs in favor of upstream's single post-execution record_outcome, which also captures the denial reason text.
  • Their bypass_reason honesty guardrail predated my process-cwd containment, so I narrowed it to three cases instead of dropping it: network is uncontained, process has a scope-checked cwd but the process itself is not sandboxed, everything else is enforced. Updated the doc line to match.

Both of their new policy tests pass unmodified alongside mine: 17 lib devin tests, 20 supervisor, 4 contract, 2 session-state.

Two things worth flagging:

  1. The local release build OOMs, CI's does not. This sandbox is capped at 8 GiB and asupersync with codegen-units=1 -C opt-level=z gets SIGKILLed even at -j 1. I could not verify the release profile locally; CI's green Build release binary step is the only evidence for it.
  2. Three more commits have landed on the branch since mine (14c35019, 0aa4096c, 663ed582) — all touching the same devin audit/policy files, including "extract bounded audit error text helper." My commit is an ancestor of the current head, but I have not verified the branch tip as a whole; those agents are actively reworking the code I just merged with.

View with [code]smith

@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: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/devin/policy.rs (1)

395-416: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Default missing process working directories before path validation.

validate_paths only checks ToolCategory::Process when the policy arguments include cwd, working_dir, or workingDir. The current exec/shell_command adapter inserts resolved cwd before calling admit, but any Process-category adapter that omits these keys receives an empty scoped-path list. Make the policy default a missing working directory to state.workspace instead of relying on adapters to preserve this invariant.

🤖 Prompt for 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.

In `@src/devin/policy.rs` around lines 395 - 416, Update validate_paths for
ToolCategory::Process to default missing PROCESS_WORKING_DIRECTORY_KEYS
arguments to state.workspace before calling resolve_scoped_path, while
preserving explicit working-directory validation. Ensure every Process request
yields a scoped path even when its adapter omits cwd, working_dir, and
workingDir.
.github/workflows/devin-ci.yml (1)

58-71: 🧹 Nitpick | 🔵 Trivial

Add a cargo cache before the compile-heavy steps.

This job runs cargo clippy --all-targets, three cargo test invocations, and cargo build --release with no cache and CARGO_INCREMENTAL: "0". Every run therefore rebuilds the dependency graph in both the test and the release profile. The timeout-minutes: 45 budget leaves little headroom once the Devin process-supervisor tests add their multi-second process waits.

Add Swatinem/rust-cache (pinned to a SHA) after the toolchain step, or split the release build into its own job.

[operational]

🤖 Prompt for 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.

In @.github/workflows/devin-ci.yml around lines 58 - 71, Add a pinned-SHA
Swatinem/rust-cache step immediately after the workflow’s Rust toolchain setup
and before the formatting, clippy, test, and release build steps. Configure it
to reuse the project’s Cargo registry, Git index, and build artifacts without
changing the existing commands or timeout behavior.
🤖 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 `@docs/devin-rust-progress.md`:
- Around line 27-33: Update the digest explanation in
docs/devin-rust-progress.md to state that a 12-hex-character SHA-256 prefix can
detect most changes when hashing the same canonical schema, but is insufficient
for exact validation, schema reconstruction, or field-level comparison. Preserve
the references to the full JSON Schema fixtures and ToolRegistry assertions.
- Around line 85-90: Update the Bypass documentation around validate_paths to
stop describing scope-checked cwd validation as sufficient containment. Either
document and implement enforced OS-level process and network restrictions for
Bypass, or deny those operations in Bypass and explicitly list the missing
containment as a blocking gap.

In `@docs/TEST_COVERAGE_MATRIX.md`:
- Around line 55-60: Update the Drift Check counts in
docs/TEST_COVERAGE_MATRIX.md from 111 to 117 for both “Current src/ inventory”
and “Source-file rows below,” preserving the existing self-consistency wording.

In `@docs/traceability_matrix.json`:
- Around line 896-899: Update the justification for the tests/devin_contract.rs
entry in the traceability matrix to state that it verifies the pinned parameter
schemas in process_tool_parameter_schemas.json and the recorded upstream
digests; remove the inaccurate reference to extracted transcript fixtures.

In `@scripts/ci/generate_parity_evidence.py`:
- Line 399: Update the function signature containing the colorized parameter,
likely synthetic_parity_log, to make colorized keyword-only by adding the
keyword-only separator before it. Preserve its default value and existing
no-argument or keyword-argument call behavior.

In `@src/devin/mod.rs`:
- Around line 18-22: Update the root re-exports in the devin module to include
PROCESS_UPDATE_SCHEMA_V1 from process and PROCESS_TOOL_DETAILS_SCHEMA_V1 from
process_tools, alongside the existing process constants, so consumers can access
both schema IDs through the devin root.

In `@src/devin/process_tools.rs`:
- Around line 487-490: Extract the repeated artifact-reference formatting into a
shared helper accepting Option<&Path> and returning Vec<String>, preserving the
existing file://{path} format. Update the block using artifact_path and
ProcessOutcome::artifact_refs to call this helper so both paths share the
audit-contract implementation.
- Around line 360-364: Update the process-tool handling around self.parse and
close_failed so validation-only failures for exec, get_output, write_to_process,
and kill_shell always have an audit record before completion. Open or push the
pending audit row before parsing, or ensure the validation-failure path creates
one before invoking close_failed, while preserving existing successful-call
audit behavior.
- Around line 753-760: Update register_process_tools to validate the tool names
produced by process_tools before extending the registry, rejecting duplicates
both within that set and against existing registry entries. Ensure registration
does not append conflicting tools, including exec, shell_command, get_output,
write_to_process, and kill_shell, and preserve the existing successful
registration behavior.
- Around line 76-92: Update the Process tool approval flow between
Agent::execute_tool and ProcessToolContext::admit so approval recorded by
audit.mark_allowed for call_id is preserved during policy re-checks. Ensure
admit recognizes the recorded Allowed decision or receives the resolved
approval, allowing approved Process calls in Normal and Smart modes while
retaining fail-closed behavior when no approval exists.

In `@src/devin/process.rs`:
- Around line 315-321: Update ProcessEntry and the supervisor spawn path to
retain the session id, then modify open_artifact to include that session id
alongside self.id in the temporary artifact filename. Preserve the existing
sanitization and file-opening behavior while ensuring artifacts from separate
supervisors cannot share a path.
- Around line 784-803: The shutdown method currently waits for each process’s
grace period serially; update it to signal all live entries first, then poll
them concurrently against a single PROCESS_TERMINATE_GRACE deadline, and
escalate all entries still running together. Preserve immediate handling of
entries already exited and the existing cleanup of terminal records after the
shared shutdown flow.
- Around line 733-768: Update write_to_process to move the ChildStdin handle out
of the ProcessLifecycle guard before calling write_all and flush, then release
the guard so lifecycle and termination paths remain usable during blocking I/O.
Preserve the close_stdin behavior, restore the handle afterward when
appropriate, and close it on write or flush failure before returning the
existing error.
- Around line 291-311: Update ingest so the chunk that triggers open_artifact is
not written again by the subsequent incremental write. Track whether this
invocation created the artifact, using the existing open_artifact flow, and skip
file.write_all(chunk) for that invocation while preserving incremental writes
for already-open artifacts.
- Around line 960-986: The emit_update streaming path currently copies and
truncates the entire retained output on every byte-count change; change the
update flow around emit_update and its run_foreground caller to send only output
added since the previous update, or otherwise enforce a fixed update interval.
Preserve the existing ToolUpdate metadata and truncation limits while ensuring
chatty processes do not repeatedly process the full retained buffer.

In `@tests/devin_process_supervisor.rs`:
- Around line 494-511: Extend the large-output test around
process_field(&output, "artifactPath") to read the referenced artifact and
assert its byte length equals the record’s stdoutBytes plus stderrBytes values.
Keep the existing artifact presence assertion, and ensure the check would detect
duplicate writes from ProcessEntry::ingest.
- Around line 736-754: Extend the process supervisor tests with a case that
pre-records an approval for the same call_id before invoking the exec tool, then
assert the command succeeds and is dispatched. Exercise the Agent::execute_tool
approval path and verify the audit status is Allowed, using the existing Harness
APIs and assertions from
normal_mode_asks_before_running_a_process_and_fails_closed_without_an_approver.
- Around line 202-218: Replace std::thread::sleep in the polling loop around
get_output with an awaited ambient Cx timer, using the test’s existing
asupersync context or establishing one via run_test. Apply the same non-blocking
timer change to the second polling loop around write_to_process, preserving the
existing polling intervals and termination conditions.

In `@tests/suite_classification.toml`:
- Line 165: Move the "devin_contract" suite entry from [suite.vcr] to
[suite.unit], placing it alongside "devin_process_supervisor" and
"devin_session_state"; leave the suite name unchanged.

---

Outside diff comments:
In @.github/workflows/devin-ci.yml:
- Around line 58-71: Add a pinned-SHA Swatinem/rust-cache step immediately after
the workflow’s Rust toolchain setup and before the formatting, clippy, test, and
release build steps. Configure it to reuse the project’s Cargo registry, Git
index, and build artifacts without changing the existing commands or timeout
behavior.

In `@src/devin/policy.rs`:
- Around line 395-416: Update validate_paths for ToolCategory::Process to
default missing PROCESS_WORKING_DIRECTORY_KEYS arguments to state.workspace
before calling resolve_scoped_path, while preserving explicit working-directory
validation. Ensure every Process request yields a scoped path even when its
adapter omits cwd, working_dir, and workingDir.
🪄 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 Plus

Run ID: 72935c59-da28-4cca-8491-30950482cb75

📥 Commits

Reviewing files that changed from the base of the PR and between 138741e and 43395d7.

📒 Files selected for processing (21)
  • .github/workflows/ci.yml
  • .github/workflows/devin-ci.yml
  • docs/TEST_COVERAGE_MATRIX.md
  • docs/devin-rust-progress.md
  • docs/traceability_matrix.json
  • scripts/ci/generate_parity_evidence.py
  • scripts/e2e/run_all.sh
  • src/agent.rs
  • src/devin/audit.rs
  • src/devin/mod.rs
  • src/devin/policy.rs
  • src/devin/process.rs
  • src/devin/process_tools.rs
  • src/devin/state.rs
  • src/session.rs
  • src/tools.rs
  • tests/devin_contract.rs
  • tests/devin_process_supervisor.rs
  • tests/devin_session_state.rs
  • tests/fixtures/devin_cli/process_tool_parameter_schemas.json
  • tests/suite_classification.toml

Comment thread docs/devin-rust-progress.md Outdated
Comment thread docs/devin-rust-progress.md Outdated
Comment thread docs/TEST_COVERAGE_MATRIX.md Outdated
Comment thread docs/traceability_matrix.json Outdated
Comment thread scripts/ci/generate_parity_evidence.py Outdated
Comment thread src/devin/process.rs Outdated
Comment thread tests/devin_process_supervisor.rs Outdated
Comment thread tests/devin_process_supervisor.rs Outdated
Comment thread tests/devin_process_supervisor.rs Outdated
Comment thread tests/suite_classification.toml Outdated

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/devin/process_tools.rs (1)

65-127: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

A recorded approval is keyed only by call_id, so it admits any later call that reuses that id. recorded_approval accepts any Allowed audit record for the matching call_id and never compares the current arguments against the arguments that were approved. evaluate upserts the row for the same call_id before that check is read, so the row already describes the new call. A second process-tool call that reuses the id therefore skips the approval surface in Normal and Smart permission modes.

  • src/devin/process_tools.rs#L65-L127: pin the approved arguments to the approval, for example by requiring the recorded argument hash to match the current arguments, or consume the approval so it admits exactly one execution.
  • tests/devin_process_supervisor.rs#L812-L855: add a case that records the approval for one command and then dispatches a different command under the same call_id, and assert that the call is denied.
🤖 Prompt for 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.

In `@src/devin/process_tools.rs` around lines 65 - 127, The approval lookup in
process_tools.rs lines 65-127 must bind approval to the approved arguments
rather than call_id alone; update recorded_approval and its caller to compare
the current arguments with the recorded approval hash, or consume the approval
after one execution. In tests/devin_process_supervisor.rs lines 812-855, add
coverage that approves one command, reuses the same call_id with different
arguments, and verifies the second call is denied.
♻️ Duplicate comments (1)
src/devin/process.rs (1)

782-810: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

The blocking stdin write still runs on the executor thread.

Releasing the lifecycle guard fixes the wedged-termination problem. The write itself is still synchronous. write_to_process is a blocking fn, and WriteToProcessTool::execute calls it directly on the runtime thread in src/devin/process_tools.rs around line 664. If the child stops draining its stdin pipe, write_all blocks and occupies that executor thread until the pipe drains or the child dies.

Move the write onto a blocking thread, or make write_to_process async and offload the write_all/flush pair.

A second effect of taking the handle out of the guard: a concurrent write_to_process for the same process id now observes lifecycle.stdin == None and reports stdin for process ... is closed. Report a distinct "a write is already in progress" error so a transient overlap is not confused with a real EOF.

#!/bin/bash
set -euo pipefail

# How does the rest of the repository offload blocking child I/O?
rg -n -C4 'spawn_blocking|block_in_place|blocking\(' src | head -80

# Confirm the call site is on the async path with no offload.
rg -n -C6 'supervisor\s*\.\s*write_to_process' src
🤖 Prompt for 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.

In `@src/devin/process.rs` around lines 782 - 810, Offload the synchronous stdin
write and flush performed by write_to_process so WriteToProcessTool::execute
does not block the async runtime thread, using the repository’s established
blocking-I/O pattern. Preserve lifecycle-lock release while the blocking
operation runs, then reacquire it to restore the handle only when still usable.
Distinguish lifecycle.stdin == None because another write temporarily owns the
handle from a genuinely closed/EOF stdin, and return a dedicated “write already
in progress” error for that overlap.
🤖 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 `@docs/TEST_COVERAGE_MATRIX.md`:
- Line 164: Update the `Source files` row and adjacent `Rust test files` row in
the Markdown coverage table by escaping the `|` characters inside their inline
shell commands, while preserving the commands and table structure.

In `@fuzz/fuzz_targets/fuzz_tool_paths.rs`:
- Around line 56-68: Update the containment assertion in the fuzz target around
resolved and raw so it only requires resolved.starts_with(&cwd) for
non-parent-traversing, relative raw inputs; exclude absolute raw paths with an
is_absolute check. Preserve the existing absolute and traversal-free invariants
for all inputs.

In `@src/devin/process_tools.rs`:
- Around line 141-168: Add an explicit row-opening method to AuditLog for
creating a pending record without assigning a policy decision, then update
close_invalid_arguments to call that method when record_for(call_id) is missing
instead of invoking policy.evaluate. Preserve the existing close_failed flow and
avoid recording any policy action for malformed calls.

In `@src/devin/process.rs`:
- Around line 336-344: Update ProcessSupervisor::open_artifact to create the
artifact through the existing tempfile dependency using an exclusive, randomly
named temporary file in the system temp directory, rather than deriving a path
from session_id and id. Preserve the existing artifact prefix where supported,
store the resulting path in ProcessOutputState, and retain secure file
permissions so concurrent supervisors cannot collide or follow attacker-created
symlinks. Verify consumers such as tests use the recorded artifactPath instead
of reconstructing the filename.

In `@tests/devin_process_supervisor.rs`:
- Around line 812-855: Extend
an_approval_recorded_before_dispatch_survives_the_adapter_policy_recheck to
evaluate and approve one command, then dispatch a different command using the
same call_id. Assert the approval is not reused across changed arguments and
verify the resulting output, audit status, and approval record match the
intended argument-binding behavior.
- Around line 546-566: Update the test around the artifact size assertion to
explicitly validate the required relationship that
PROCESS_ARTIFACT_THRESHOLD_BYTES is less than or equal to
PROCESS_STREAM_BUFFER_BYTES before relying on the produced-byte equality, or
instead calculate the expected seeded-plus-appended byte count. At the end of
the test, remove the artifact file referenced by artifact_path so it does not
remain in the shared temporary directory.
- Around line 250-253: The polling loops in run_test currently use ambient
wall-clock sleeping, bypassing the test runtime timer. Replace both occurrences,
including the one near line 311, with cx.time().sleep(...) using the existing
duration so they yield through the runtime timer consistently with
ProcessSupervisor::run_foreground.

---

Outside diff comments:
In `@src/devin/process_tools.rs`:
- Around line 65-127: The approval lookup in process_tools.rs lines 65-127 must
bind approval to the approved arguments rather than call_id alone; update
recorded_approval and its caller to compare the current arguments with the
recorded approval hash, or consume the approval after one execution. In
tests/devin_process_supervisor.rs lines 812-855, add coverage that approves one
command, reuses the same call_id with different arguments, and verifies the
second call is denied.

---

Duplicate comments:
In `@src/devin/process.rs`:
- Around line 782-810: Offload the synchronous stdin write and flush performed
by write_to_process so WriteToProcessTool::execute does not block the async
runtime thread, using the repository’s established blocking-I/O pattern.
Preserve lifecycle-lock release while the blocking operation runs, then
reacquire it to restore the handle only when still usable. Distinguish
lifecycle.stdin == None because another write temporarily owns the handle from a
genuinely closed/EOF stdin, and return a dedicated “write already in progress”
error for that overlap.
🪄 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 Plus

Run ID: 5911bbec-d51d-4a51-932c-b33e889aaf84

📥 Commits

Reviewing files that changed from the base of the PR and between 43395d7 and efa0861.

📒 Files selected for processing (12)
  • .github/workflows/bench.yml
  • docs/TEST_COVERAGE_MATRIX.md
  • docs/devin-rust-progress.md
  • docs/traceability_matrix.json
  • fuzz/fuzz_targets/fuzz_tool_paths.rs
  • scripts/ci/generate_parity_evidence.py
  • src/devin/mod.rs
  • src/devin/process.rs
  • src/devin/process_tools.rs
  • tests/devin_contract.rs
  • tests/devin_process_supervisor.rs
  • tests/suite_classification.toml

Comment thread docs/TEST_COVERAGE_MATRIX.md Outdated
Comment thread fuzz/fuzz_targets/fuzz_tool_paths.rs Outdated
Comment on lines +56 to +68
// Resolution is purely lexical: the input is joined onto `cwd` and its
// dot segments are collapsed, so `..` segments legitimately climb above
// `cwd` (`".."` resolves to `/tmp`). Keeping reads and writes inside the
// working directory is a scope-enforcement concern handled separately by
// `enforce_read_scope`, not a normalization invariant. What resolution
// must guarantee is that joining onto an absolute `cwd` always yields an
// absolute, traversal-free path, and that inputs which never climb stay
// under `cwd`.
assert!(resolved.is_absolute());
assert!(!has_parent_dir(&resolved));
if !has_parent_dir(Path::new(&raw)) {
assert!(resolved.starts_with(&cwd));
}

@coderabbitai coderabbitai Bot Aug 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 12 \
  'raw|cwd|join\(|fuzz_normalize_dot_segments|enforce_.*scope|is_absolute|starts_with' \
  fuzz/fuzz_targets/fuzz_tool_paths.rs src/tools.rs

Repository: OnlineChefGroep/pi_agent_rust

Length of output: 50386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- fuzz target files ---\n'
git ls-files 'fuzz/fuzz_targets/*' | sed -n '1,120p'

printf '\n--- fuzz_tool_paths.rs outline and size ---\n'
wc -l fuzz/fuzz_targets/fuzz_tool_paths.rs
ast-grep outline fuzz/fuzz_targets/fuzz_tool_paths.rs || true

printf '\n--- fuzz_tool_paths.rs lines 1-140 ---\n'
sed -n '1,140p' fuzz/fuzz_targets/fuzz_tool_paths.rs

printf '\n--- relevant definitions and paths search ---\n'
rg -n --glob 'src/**/*.rs' --glob 'fuzz/**/*.rs' \
  'enforce_read_scope|enforce_write_scope|fuzz_normalize_dot_segments|fuzz_tool_paths|normalize_dot_segments|resolve_relative|Path::new|cwd\.path|join\(\)' \
  | sed -n '1,240p'

Repository: OnlineChefGroep/pi_agent_rust

Length of output: 258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- fuzz target files ---'
git ls-files 'fuzz/fuzz_targets/*' | sed -n '1,120p'

echo
echo '--- fuzz_tool_paths.rs size ---'
wc -l fuzz/fuzz_targets/fuzz_tool_paths.rs

echo
echo '<!-- result-start -->'
echo 'FURTHER_INSPECTION_REQUIRED'
echo '<!-- result-end -->'

Repository: OnlineChefGroep/pi_agent_rust

Length of output: 995


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- fuzz_tool_paths.rs lines 1-80 ---'
cat -n fuzz/fuzz_targets/fuzz_tool_paths.rs | sed -n '1,80p'

echo
echo '--- relevant definitions/usages with focused paths ---'
rg -n -C 6 'enforce_read_scope|enforce_write_scope|normalize_dot_segments|dots|has_parent_dir|working_directory|working dir|outside the working|absolute|Path::new|join\(' fuzz/fuzz_targets/fuzz_tool_paths.rs src | sed -n '1,260p'

echo
echo '--- all scope-related declarations ---'
rg -n 'fn enforce_read_scope|fn enforce_write_scope|fn normalize_dot_segments|fn .*dot_segments|has_parent_dir' src fuzz/fuzz_targets/fuzz_tool_paths.rs

Repository: OnlineChefGroep/pi_agent_rust

Length of output: 22199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- src/tools.rs path resolution and scope enforcement ---'
sed -n '2040,2200p' src/tools.rs

echo
echo '--- behavioral probe mirroring fuzz_tool_paths control flow ---'
python3 - <<'PY'
from pathlib import PurePosixPath

def has_parent_dir(path):
    return any(part in ('..', '.') for part in str(path).split('/'))

def resolve(raw, cwd):
    p = PurePosixPath(cwd)
    q = PurePosixPath(raw)
    # Rust PathBuf::join behavior for the relevant cases:
    if q.is_absolute():
        return q
    return p / q

cws = ['/tmp/pi-agent-rust-fuzz-root']
samples = ['/etc/passwd', '/tmp/pi-agent-rust-fuzz-root', '/tmp/pi-agent-rust-fuzz-root/../x', 'sandbox/../x', '..', 'sandbox/x']
for raw in samples:
    resolved = PurePosixPath(resolve(raw, cws[0]))
    starts = str(resolved).startswith(cws[0] + '/') or resolved == PurePosixPath(cws[0])
    print(raw, "=>", resolved, "has_parent_dir_raw=", has_parent_dir(PurePosixPath(raw)), "starts_with_cwd=", starts)
PY

Repository: OnlineChefGroep/pi_agent_rust

Length of output: 7226


Exclude absolute raw paths from the cwd containment assertion.

The target accepts all lossy string inputs, including absolute paths like /etc/passwd; such inputs are not joined onto cwd, so resolved.starts_with(&cwd) is false for the intended absolute path value. Add an is_absolute guard here or split absolute inputs into their own scope-enforcement assertion.

Possible guard
-        if !has_parent_dir(Path::new(&raw)) {
+        if !Path::new(&raw).is_absolute()
+            && !has_parent_dir(Path::new(&raw))
+        {
             assert!(resolved.starts_with(&cwd));
         }
📝 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
// Resolution is purely lexical: the input is joined onto `cwd` and its
// dot segments are collapsed, so `..` segments legitimately climb above
// `cwd` (`".."` resolves to `/tmp`). Keeping reads and writes inside the
// working directory is a scope-enforcement concern handled separately by
// `enforce_read_scope`, not a normalization invariant. What resolution
// must guarantee is that joining onto an absolute `cwd` always yields an
// absolute, traversal-free path, and that inputs which never climb stay
// under `cwd`.
assert!(resolved.is_absolute());
assert!(!has_parent_dir(&resolved));
if !has_parent_dir(Path::new(&raw)) {
assert!(resolved.starts_with(&cwd));
}
// Resolution is purely lexical: the input is joined onto `cwd` and its
// dot segments are collapsed, so `..` segments legitimately climb above
// `cwd` (`".."` resolves to `/tmp`). Keeping reads and writes inside the
// working directory is a scope-enforcement concern handled separately by
// `enforce_read_scope`, not a normalization invariant. What resolution
// must guarantee is that joining onto an absolute `cwd` always yields an
// absolute, traversal-free path, and that inputs which never climb stay
// under `cwd`.
assert!(resolved.is_absolute());
assert!(!has_parent_dir(&resolved));
if !Path::new(&raw).is_absolute()
&& !has_parent_dir(Path::new(&raw))
{
assert!(resolved.starts_with(&cwd));
}
🤖 Prompt for 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.

In `@fuzz/fuzz_targets/fuzz_tool_paths.rs` around lines 56 - 68, Update the
containment assertion in the fuzz target around resolved and raw so it only
requires resolved.starts_with(&cwd) for non-parent-traversing, relative raw
inputs; exclude absolute raw paths with an is_absolute check. Preserve the
existing absolute and traversal-free invariants for all inputs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The containment assertion already sits inside the else if !is_tilde branch, which is only reached when Path::new(&raw).is_absolute() is false, so the proposed is_absolute guard is redundant.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Skipped: comment is from another GitHub bot.

Comment thread src/devin/process_tools.rs Outdated
Comment thread src/devin/process.rs Outdated
Comment thread tests/devin_process_supervisor.rs Outdated
Comment on lines +250 to +253
// Ambient timer, not `std::thread::sleep`: this loop runs inside a
// `run_test` future and must not block the executor thread the
// supervisor's own `cx.time().sleep` depends on.
asupersync::time::sleep(asupersync::time::wall_now(), Duration::from_millis(100)).await;

@coderabbitai coderabbitai Bot Aug 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# What clock does run_test install, and what does sleep expect as its first argument?
rg -n -C6 'pub fn run_test|pub fn wall_now|pub async fn sleep' "$(dirname "$(fd -HI 'Cargo.lock' -d1 | head -1)")" 2>/dev/null || true

# How do the other tests in this repository obtain a timer inside run_test?
rg -n -C4 'asupersync::time::sleep|cx\.time\(\)\.sleep|wall_now' tests src | head -60

Repository: OnlineChefGroep/pi_agent_rust

Length of output: 1976


🌐 Web query:

asupersync time::sleep wall_now virtual time run_test

💡 Result:

In the asupersync library, the functions sleep, wall_now, and virtual time management are central to its deterministic "lab" runtime, designed to enable reproducible testing of concurrent async Rust code [1][2][3]. Asupersync replaces implicit clock-reading with explicit time-handling to ensure tests are deterministic [1]. Instead of calling tokio::time::sleep, which implicitly reads the system clock, you use asupersync::time::sleep, which requires passing the current time as an argument [1][2]. Key components include: 1. sleep(now, duration): This function takes a Time object (often provided by the current task's timer driver) and a duration [1][2]. In the production runtime, this behaves like standard wall-clock time, while in the deterministic lab runtime, it uses virtual time, where time only advances when explicitly controlled by the scheduler [4][1][2]. 2. wall_now: This function is typically used to retrieve the current time in the production/real-world context [4][5]. When running tests in the lab, code often uses a pattern to check for a timer driver; if one exists (the lab runtime), it uses the driver's time; otherwise, it falls back to wall_now [4][5]. 3. Virtual Time and Testing: The lab runtime provides a deterministic virtual time wheel [1][2]. Tests run within a LabRuntime, which allows the scheduler to control the passage of time [4][1]. Because all time-based events are governed by this virtual clock, tests can execute instantly and repeatedly with the same seed, producing identical execution traces [4][1][6]. A typical test pattern in asupersync involves initializing a LabRuntime, creating a scope, and spawning tasks that interact with this controlled time environment to verify behavior without non-deterministic real-world timing dependencies [4][1].

Citations:


🌐 Web query:

asupersync source time sleep wall_now pub fn wall_now pub async fn sleep

💡 Result:

Asupersync is a Rust async runtime designed for structured concurrency, cancel-correctness, and deterministic testing [1][2][3]. In the asupersync::time module, both wall_now and sleep are core primitives used to manage time-based operations within the runtime's capability-aware context [4][5]. wall_now The wall_now function is a global or ambient accessor used to retrieve the current time [5][6]. It is typically used as a fallback when a task-specific timer driver is not explicitly available [6][7][8]. sleep The sleep function in asupersync is designed to be cancel-aware and requires an explicit "now" timestamp to operate, reflecting the runtime's emphasis on deterministic scheduling and explicit capability handling [5][9]. The function signature is sleep(now: Time, duration: Duration) -> Sleep, where the now parameter ensures that the sleep timer is bound to the correct timeline (whether that is the wall clock or a simulated, deterministic driver) [5][6][9]. Usage Pattern In production code, developers often determine the "now" value dynamically by checking the current context (Cx) for a timer driver before falling back to wall_now [6][7][9]. This ensures that if the code is running under a test-controlled runtime (e.g., for replay debugging), it uses the simulated time, and if it is running in production, it uses the system wall clock [5][6][8]. Example pattern: let now = cx.timer_driver.map_or_else(asupersync::time::wall_now, |driver| driver.now); asupersync::time::sleep(now, duration).await; [5][7][8]

Citations:


🌐 Web query:

GitHub asupersync docs.rs asupersync run_test wall_now time::sleep

💡 Result:

Asupersync is a Rust async runtime designed with a focus on "cancel-correctness," structured concurrency, and deterministic testing [1][2][3]. It differs from runtimes like Tokio by enforcing structural ownership of tasks via hierarchical "regions," which guarantee that all child tasks complete before a region closes [4][5]. Regarding your query about run_test, wall_now, and time::sleep, these are components of Asupersync's testing and timing infrastructure: 1. Deterministic Lab Runtime: Asupersync includes a lab module designed for deterministic testing [3][6]. This runtime provides a controlled environment where concurrency is reproducible and can be replayed [4][2]. 2. Timing Primitives: The asupersync::time module contains primitives for asynchronous timing, including sleep, timeout, and interval operations [3][6]. 3. Testing/Execution: While official documentation often refers to the lab runtime for testing [3][7], methods such as run_test (often used within the lab context) allow developers to execute tests using virtual time, enabling deterministic verification of code that relies on timing primitives like time::sleep [4]. Instead of using real system time (which is non-deterministic), the lab runtime's wall_now and associated timing tools allow the runtime to simulate the passage of time accurately during test execution [4][8]. For the most up-to-date implementation details, you can refer to the official documentation on docs.rs [3] or the repository on GitHub [4]. Sources: [1] https://asupersync.com/ [4] https://github.com/Dicklesworthstone/asupersync [2] https://crates.io/crates/asupersync [3] https://docs.rs/asupersync/latest/asupersync/ [5] https://dicklesworthstone.github.io/asupersync/ [6] https://docs.rs/asupersync [7] https://docs.rs/asupersync/latest/asupersync/index.html [8] https://docs.rs/asupersync/latest/asupersync/runtime/index.html

Citations:


🌐 Web query:

asupersync GitHub asupersync::time::wall_now runtime virtual time context::Cx

💡 Result:

Asupersync is an asynchronous runtime for Rust designed for structural correctness, focusing on region-owned tasks, cancel-correct protocols, and capability-gated effects [1][2]. The runtime supports deterministic replay testing, which relies on a virtual time system [2][3]. Regarding the specific components requested: 1. asupersync::time::wall_now: This represents the mechanism for accessing real-world wall-clock time [4]. In the production runtime, Time corresponds to wall-clock time, while in the lab runtime, it is virtual time controlled by the scheduler [4]. 2. Runtime virtual time: Asupersync allows for deterministic testing by decoupling time from the wall clock [5][3]. In the lab runtime, time only advances when explicitly commanded by the scheduler [3]. APIs such as sleep(now, dur) take the current Time as an argument rather than reading the clock implicitly, ensuring compatibility with the virtual time system [5]. 3. context::Cx: The Cx struct serves as a capability-carrying context that is passed to async operations [5][3]. It acts as the conduit for runtime-managed effects (such as I/O, timers, and cancellation), ensuring that all non-deterministic effects are properly intercepted and made replayable [3]. By requiring &Cx to be threaded through async tasks, the runtime enforces that effects remain gated and verifiable within the system's capability model [2][5][3]. These mechanisms are central to Asupersync's design, which treats system effects as structural contracts that must be satisfied for task execution and reliable testing [1][2][5].

Citations:


🌐 Web query:

github.com Dicklesworthstone asupersync asupersync time.rs wall_now run_test

💡 Result:

The symbols asupersync, time.rs, wall_now, and run_test refer to components and usage patterns within the Asupersync project, an experimental, spec-first async runtime for Rust developed by user Dicklesworthstone [1][2][3]. Asupersync is designed for deterministic, cancel-correct concurrency [1][4][5]. The project utilizes a lab runtime to facilitate deterministic testing, trace capture, and replay, which allows developers to test async code with virtual time and controlled schedules [1][6][2]. Regarding the specific components mentioned: - asupersync: This is the core crate/runtime [2][4]. It provides a structural approach to task management where tasks are owned by regions, ensuring correct cancellation and quiescence [1][5]. - time.rs: In the context of Asupersync, src/time/ is the module responsible for time-related abstractions [1][7]. The runtime differentiates between virtual time (used in the lab environment for deterministic execution) and system wall-clock time [1][2][7]. - wall_now: This is a function (exported as asupersync::time::wall_now) used to retrieve the current wall-clock time [6][8]. It is often used when creating budgets or timeouts that need to track against real time rather than the runtime's internal virtual clock [8]. - run_test: This is not a single defined function, but rather a pattern used in Asupersync's testing suite [1]. The runtime provides utilities like LabRuntime which expose methods such as run_until_quiescent() or run_until_quiescent_with_report() to drive asynchronous tasks to completion for verification in unit tests [1][6]. For developers, Asupersync requires explicit capability management [2][4]. For example, when performing timing operations, developers often use sleep(now, duration) instead of implicit clock reading, passing the now (retrieved from the task context or wall_now) to ensure compatibility with virtual time [1][6][2][7]. Code that requires a wall-clock deadline often utilizes Budget::new().with_timeout(wall_now(), Duration::from_secs(n)) [8].

Citations:


🌐 Web query:

asupersync GitHub asupersync::time::sleep function wall_now source

💡 Result:

In the asupersync Rust crate, the asupersync::time::sleep function is an asynchronous utility designed for capability-secure, deterministic time management [1][2]. Unlike standard async runtimes that implicitly read the clock, asupersync::time::sleep requires an explicit starting time parameter (typically obtained via a timer driver or a wall clock source) and a duration [1][3]. The wall_now function, found in the asupersync::time module, serves as the default source for the current wall-clock time [2][4]. It is commonly used as a fallback when no specific timer driver is available in the task's execution context, as demonstrated in the project's documentation and integration examples [2][4]: let now = task_cx.timer_driver.map_or_else(wall_now, |driver| driver.now); let timer_future = sleep(now, Duration::from_millis(100)).await; By requiring the explicit now parameter, the runtime enables features like virtual time in its Lab runtime, allowing for deterministic testing where timers do not depend on the system clock [1][3][5].

Citations:


🌐 Web query:

site:github.com/Dicklesworthstone/asupersync asupersync::time asupersync::time::sleep wall_now context.run_test

💡 Result:

In the asupersync framework, the time and testing primitives are designed to support deterministic, capability-based asynchronous execution [1][2]. asupersync::time::sleep Unlike the standard tokio::time::sleep, which reads the clock implicitly, asupersync::time::sleep requires the current time (often provided via cx.now) as an argument [1][2]. This design ensures compatibility with virtual time environments, such as those used in laboratory runtimes for deterministic testing [1][2]. asupersync::time::wall_now This function retrieves the current wall-clock time [3]. It serves as a fallback or source of truth when a specific timer driver is not available or when operations require actual system time (e.g., for certain network handshakes or external deadlines) [3]. context.run_test The provided query string "context.run_test" likely refers to a pattern or test helper used within the asupersync ecosystem for executing tests with controlled runtimes. As noted in documentation on replay debugging and integration, tests often utilize a LabRuntime or similar structure to manage virtual time and deterministic scheduling [4][5]. Developers typically use a Cx (context) object to thread capabilities—such as timers and spawners—through their async tasks, ensuring that time-based events can be controlled, replayed, and verified [1][2]. In summary, these primitives allow asupersync to maintain a strict capability-based model where time-dependent operations are explicit, facilitating features like virtual time, test replaying, and schedule exploration [2].

Citations:


Use cx.time().sleep(…) inside run_test.

run_test installs the Ambient Timer, and ProcessSupervisor::run_foreground also uses cx.time().sleep(POLL_TICK). asupersync::time::wall_now() resolves the real wall clock instead of the runtime timer, so these polling loops can not yield as expected inside a test runtime. Apply the same change to line 311.

🤖 Prompt for 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.

In `@tests/devin_process_supervisor.rs` around lines 250 - 253, The polling loops
in run_test currently use ambient wall-clock sleeping, bypassing the test
runtime timer. Replace both occurrences, including the one near line 311, with
cx.time().sleep(...) using the existing duration so they yield through the
runtime timer consistently with ProcessSupervisor::run_foreground.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

run_test(|| async { … }) provides no Cx to call cx.time().sleep on, and asupersync::time::sleep(wall_now(), …) is the established pattern for polling loops across this test suite.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Skipped: comment is from another GitHub bot.

Comment thread tests/devin_process_supervisor.rs Outdated
Comment thread tests/devin_process_supervisor.rs Outdated
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