Skip to content

feat(shell,approval-gate)!: permissive coder, unified error taxonomy and redaction (MOT-4099, MOT-4104, MOT-4105) - #542

Merged
ytallo merged 5 commits into
mainfrom
feat/shell-dx-permissive
Jul 20, 2026
Merged

feat(shell,approval-gate)!: permissive coder, unified error taxonomy and redaction (MOT-4099, MOT-4104, MOT-4105)#542
ytallo merged 5 commits into
mainfrom
feat/shell-dx-permissive

Conversation

@ytallo

@ytallo ytallo commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Implements MOT-4099, MOT-4104, MOT-4105 — one commit per ticket.

Context

On the shipped permissive default (fs.allow_unjailed: true, empty fs.host_roots), shell::exec/shell::fs::* reach the whole filesystem while coder::* stayed jailed to engine-cwd + /tmp. Picking a harness working directory outside the engine cwd made every coder::* call fail with scope_root is outside every allowed root (Anthony's report), and models routed around it via shell::exec. A follow-up DX audit found the drift is systemic: colliding error codes, opposite existence-redaction postures, one-way cross-referencing, and a latent gap where unjailed shell::fs::* skipped non_accessible_globs entirely.

Changes

1. coder::* follows the unjailed deny-only policy — MOT-4099 (bee3f8f3)

  • With the operator opt-in (fs.allow_unjailed: true + empty fs.host_roots), the coder resolver mirrors shell::fs::*: absolute paths anywhere on the host; the cwd + /tmp fallback roots demoted to relative-path anchors; the harness-stamped fs_scope.root trusted as the anchor under the configured_roots boundary (matching shell::exec's cwd contract).
  • The workspace boundary keeps session scoping, so the folder-approval flow still triggers where approval-gate is installed. Jailed deployments (explicit fs.host_roots) are byte-for-byte unchanged.
  • fs.denylist_paths now applies to coder::* in every mode (redacted C211).
  • Closes the unjailed secrets gap: non_accessible_globs now match the root-stripped absolute form when no root contains the path — previously unjailed shell::fs::* silently skipped the glob check.
  • coder::info reports the effective mode: jailed | unjailed.

2. One error taxonomy + one redaction posture — MOT-4104 (4d6480bb) BREAKING

  • Coder-only renames so equal digits mean the same failure class on both surfaces (S-codes untouched):

    meaning before after
    already exists C217 C213
    size cap exceeded C213 C218
    outside session scope C218 C220
  • shell::fs::* adopts coder's redaction invariant: permission-denied, protected-glob, and fs.denylist_paths rejections fold into S211 with the single "not found or not accessible" wording. S215 is now exclusively a jail-confinement escape (it keeps carrying the filesystem_access_request hint that drives the approval flow).

  • approval-gate JAIL_SCOPE_CODES: C218C220.

3. Two-way discoverability + conventions — MOT-4105 (268f0edb)

  • Every shell::fs::* description now points at its coder::* twin (the reverse link already existed), with error hints updated to the new semantics.
  • README "Two surfaces, one contract": twin-operation table, shared code/redaction/batching/naming conventions, discovery map.

Deploy note — lockstep pair

shell/v0.10.0 and approval-gate/v1.0.9 must ship in the same release wave: a stale approval-gate will not prompt on C220 coder session-escapes until upgraded (accepted trade-off; no dual-accept bridge).

Verification

  • shell: 1308 passed / 0 failed (unit + integration + BDD + goldens re-blessed, diffs reviewed); approval-gate: 175/0; worktree: 101/0 (its S215 land-gate matcher is unaffected — S-codes unchanged); clippy clean.
  • Not yet exercised: live four-worker E2E (dev stack, workspace outside engine cwd → coder::tree works; console badge; approval prompt on C220 in jailed mode). Recommend before cutting the release tags.

Summary by CodeRabbit

  • New Features

    • Added jailed and unjailed access modes, with coder::info now reporting the effective mode.
    • Added deny-only filesystem access with path denylists and non-accessible glob protections.
    • Unified access behavior across filesystem and coder operations.
  • Bug Fixes

    • Protected, denied, and missing paths now use consistent redacted S211 errors.
    • Updated error-code assignments for size limits, overwrite conflicts, and session escapes.
  • Documentation

    • Added upgrade guidance and updated schemas, examples, and error-code references for releases 0.10.0 and 1.0.9.

ytallo added 3 commits July 18, 2026 00:24
With the operator opt-in (fs.allow_unjailed: true, empty fs.host_roots) the
coder resolver now mirrors shell::fs::*: absolute paths anywhere on the host,
the cwd + /tmp fallback roots demoted to relative-path anchors, and the
harness-stamped fs_scope.root trusted as the anchor under the configured_roots
boundary — matching shell::exec's cwd contract. fs.denylist_paths now applies
to coder::* in every mode (redacted C211). Jailed deployments (explicit
fs.host_roots) are unchanged, and the workspace boundary keeps its session
scoping so the approval flow still triggers.

Also closes the unjailed secrets gap in shell::fs::*: non_accessible_globs
now match the root-stripped absolute form when no root contains the path,
so **/.env-style protection holds with empty host_roots.

coder::info gains a mode field (jailed | unjailed).
… posture (MOT-4104)

Renumber the three drifted coder codes so equal digits mean the same failure
class on both surfaces: already-exists C217 -> C213, too-large C213 -> C218,
outside-session C218 -> C220. S-codes are untouched. approval-gate's
JAIL_SCOPE_CODES tracks the C220 rename (lockstep release with shell 0.10.0).

Unify existence redaction on coder's C211 invariant: shell::fs::* now folds
permission-denied, protected-glob, and fs.denylist_paths rejections into S211
with the single 'not found or not accessible' wording. S215 is exclusively a
jail-confinement escape (it keeps carrying the filesystem_access_request hint
that drives the folder-approval flow).

shell 0.9.2 -> 0.10.0, approval-gate 1.0.8 -> 1.0.9; README tables, upgrade
notes, and CHANGELOG updated; goldens re-blessed.
…ions (MOT-4105)

Every shell::fs::* description now points at its coder::* twin (the reverse
link already existed), with the error-code hints updated to the 0.10.0
semantics (S211 not-found-or-not-accessible, S215 jail escape only). README
gains a 'Two surfaces, one contract' section: the twin-operation table, the
shared code/redaction/batching/naming conventions, and the discovery map.
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 20, 2026 8:17pm
workers-tech-spec Ready Ready Preview, Comment Jul 20, 2026 8:17pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR releases shell 0.10.0 and approval-gate 1.0.9, aligns coder error codes with shell filesystem codes, unifies denied-path redaction under S211, adds unjailed resolver behavior and coder::info access-mode reporting, and updates documentation and tests.

Changes

Access contract and filesystem behavior

Layer / File(s) Summary
Error-code and approval contracts
shell/src/code/error.rs, shell/src/fs/error.rs, approval-gate/...
Coder error codes are renumbered, filesystem denial errors use redacted S211 responses, and approval-gate jail-scope handling changes from C218 to C220.
Unjailed resolver configuration and path enforcement
shell/src/code/config.rs, shell/src/code/path.rs, shell/src/config.rs, shell/src/configuration.rs
Unjailed and denylist settings flow into PathResolver; containment, glob matching, session roots, and scope resolution apply the updated rules.
Unified filesystem redaction
shell/src/fs/host.rs, shell/src/exec/policy.rs, shell/src/functions/workspace.rs
Missing, permission-denied, protected, and denylisted paths return the same redacted S211 shape across filesystem operations.
Coder API mode and budget contracts
shell/src/code/functions/*, shell/tests/golden/schemas/*, shell/tests/golden/errors.json
coder::info reports jailed or unjailed mode, while coder budget, overwrite, session, and schema expectations use the revised codes.
Release metadata and behavioral validation
shell/README.md, shell/CHANGELOG.md, shell/tests/*, approval-gate/tests/*
Release documentation, migration notes, golden schemas, feature tests, and end-to-end expectations describe and validate the new contracts.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Configuration
  participant PathResolver
  participant FilesystemSurface
  participant CoderInfo
  Configuration->>PathResolver: Configure jailed or unjailed mode
  PathResolver->>FilesystemSurface: Resolve paths with denylist and glob checks
  FilesystemSurface-->>PathResolver: Return resolved path or redacted S211
  PathResolver->>CoderInfo: Expose unjailed state
  CoderInfo-->>Configuration: Return mode as jailed or unjailed
Loading

Possibly related PRs

  • iii-hq/workers#481: Both changes cover zero-config seed and coder resolver behavior around unjailed fallback conditions.

Poem

A rabbit hops through roots so wide,
With S211 tucked safe inside.
C220 guards the session gate,
Unjailed paths now know their fate.
“Info” tells which mode we’re in—
Then carrot-colored tests all win!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: unjailed coder behavior, unified error codes/redaction, and shell/approval-gate updates.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/shell-dx-permissive

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.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 47 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

ytallo added 2 commits July 20, 2026 17:14
…action

The three harness E2E cases that pinned denylist rejections as S215 now
expect the redacted S211 (denylisted reads exactly like missing; S215 is
jail-escape only). Symlink jail-escape cases keep S215 — unchanged.
@ytallo
ytallo merged commit 911254a into main Jul 20, 2026
18 of 19 checks passed

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

Caution

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

⚠️ Outside diff range comments (1)
shell/src/fs/host.rs (1)

545-584: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Denylist check must run before the is_dir check to actually satisfy the redaction invariant.

Right now a missing scope_root hits the !canon.is_dir() branch (S212) before the denylist loop ever runs, while an existing denylisted directory reaches the denylist loop and returns S211. That lets a caller distinguish "directory exists and is denylisted" (S211) from "path doesn't exist" (S212) — the exact probe the comment on lines 578-580 says this code prevents. shell::exec::policy::confine_scope_root (same PR) gets this right by running the denylist loop first, so denylisted-or-missing both collapse to S211.

🔒 Proposed fix: check denylist before directory type
     let canon = canonicalize_with_fallback(p).map_err(|e| {
         let msg = format!("{e}");
         if msg.contains("dangling symlink in path") {
             FsError::new("S215", format!("{scope_root}: {msg}"))
         } else {
             FsError::new("S210", format!("{scope_root}: {msg}"))
         }
     })?;
-    if !canon.is_dir() {
-        return Err(FsError::new(
-            "S212",
-            format!("scope_root is not a directory: {scope_root}"),
-        ));
-    }
     for deny_canon in denylist_canon {
         if canon.starts_with(deny_canon) {
             // REDACTION INVARIANT: denylisted folds into the same S211 as
             // missing, so callers cannot probe operator-denied directories.
             return Err(FsError::not_found_or_denied(scope_root));
         }
     }
+    if !canon.is_dir() {
+        return Err(FsError::new(
+            "S212",
+            format!("scope_root is not a directory: {scope_root}"),
+        ));
+    }
     Ok(Some(canon))
🤖 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 `@shell/src/fs/host.rs` around lines 545 - 584, Move the denylist loop in
confine_scope_root to immediately after canonicalization and before the
canon.is_dir() check, so denylisted paths consistently return
FsError::not_found_or_denied (S211) whether the target exists or is missing.
Preserve the existing S212 response for non-denylisted paths that are not
directories.
🧹 Nitpick comments (4)
shell/src/code/error.rs (1)

261-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update test names to reflect the new error code.

The error code for AlreadyExists was updated from C217 to C213, but the test names still mention c217.

♻️ Proposed refactor
-    fn io_for_path_already_exists_maps_to_c217_with_path_prefix() {
+    fn io_for_path_already_exists_maps_to_c213_with_path_prefix() {
         let e = CoderError::io_for_path(
             std::io::Error::new(std::io::ErrorKind::AlreadyExists, "exists"),
             "some/file.txt",
         );
         assert_eq!(e.code(), "C213");
         assert!(
             e.message().starts_with("some/file.txt: "),
             "C213 via io_for_path must prefix the caller path: {}",
             e.message()
         );
     }
 
     #[test]
-    fn io_already_exists_maps_to_c217() {
+    fn io_already_exists_maps_to_c213() {
         let e: CoderError = std::io::Error::new(std::io::ErrorKind::AlreadyExists, "x").into();
         assert_eq!(e.code(), "C213");
     }
🤖 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 `@shell/src/code/error.rs` around lines 261 - 278, Rename the tests
io_for_path_already_exists_maps_to_c217_with_path_prefix and
io_already_exists_maps_to_c217 to use c213, matching the assertions and current
AlreadyExists error code; leave their test logic unchanged.
shell/src/code/path.rs (1)

1204-1205: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale _c218 suffixes in test names now asserting C220.

resolve_in_absolute_inside_root_but_outside_scope_root_rejected_c218_naming_session, resolve_in_absolute_in_other_root_outside_scope_root_is_c218, and resolve_in_dotdot_within_root_but_outside_base_is_c218 all assert err.code() == "C220" but their names still say c218 — leftover from the pre-renumbering code. Purely cosmetic (grep-for-code-name confusion), no behavior impact.

♻️ Suggested rename
-    fn resolve_in_absolute_inside_root_but_outside_scope_root_rejected_c218_naming_session() {
+    fn resolve_in_absolute_inside_root_but_outside_scope_root_rejected_c220_naming_session() {
...
-    fn resolve_in_absolute_in_other_root_outside_scope_root_is_c218() {
+    fn resolve_in_absolute_in_other_root_outside_scope_root_is_c220() {
...
-    fn resolve_in_dotdot_within_root_but_outside_base_is_c218() {
+    fn resolve_in_dotdot_within_root_but_outside_base_is_c220() {

Also applies to: 1240-1241, 1367-1368

🤖 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 `@shell/src/code/path.rs` around lines 1204 - 1205, Rename the three
tests—resolve_in_absolute_inside_root_but_outside_scope_root_rejected_c218_naming_session,
resolve_in_absolute_in_other_root_outside_scope_root_is_c218, and
resolve_in_dotdot_within_root_but_outside_base_is_c218—to use the C220 suffix,
matching their err.code() assertions. Do not change test behavior or assertions.
shell/src/fs/host.rs (1)

453-482: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting this glob fallback into a shared helper.

This root-relative-else-absolute-form matching logic is functionally duplicated in the coder path resolver (per the comment "the same fallback the coder resolver uses"). The MOT-4099 gap this PR fixes had to be independently found and patched in both places — a shared helper (crate-internal fs-utils) for "match glob against root-relative form, falling back to absolute-form when uncontained" would remove that drift risk for future policy changes.

🤖 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 `@shell/src/fs/host.rs` around lines 453 - 482, Extract the uncontained-path
glob fallback from path_is_non_accessible into a shared crate-internal fs
utility, then update both path_is_non_accessible and the coder path resolver to
use it. Preserve root-relative matching for contained paths and absolute-form
matching only when no configured root contains the path, including the existing
slash normalization and empty-path handling.
shell/src/code/functions/read_file.rs (1)

1496-1496: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test names contain outdated error codes.

The assertions and documentation correctly test for the new error codes, but the corresponding test function names still include the old codes from before the taxonomy rename.

  • shell/src/code/functions/read_file.rs#L1496-L1496: rename the test batch_zero_budget_entry_c213_names_key_and_value to use c218.
  • shell/src/code/functions/read_file.rs#L2087-L2087: rename the test full_read_over_output_budget_returns_recovery_c213 to use c218.
  • shell/src/code/functions/read_file.rs#L2189-L2189: rename the test denied_huge_file_is_c211_not_c213 to use c218.
  • shell/src/code/functions/move_file.rs#L699-L699: rename the test overwrite_false_dst_exists_c217 to use c213.
🤖 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 `@shell/src/code/functions/read_file.rs` at line 1496, Rename the outdated test
functions to match their current error codes: in
shell/src/code/functions/read_file.rs at lines 1496-1496, 2087-2087, and
2189-2189, change c213 to c218 in the named tests; in
shell/src/code/functions/move_file.rs at line 699, change
overwrite_false_dst_exists_c217 to overwrite_false_dst_exists_c213. No assertion
or implementation changes are needed.
🤖 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 `@shell/README.md`:
- Around line 189-198: Add the missing S220 entry to the README’s S2xx Errors
table, documenting it as the fs twin for C220 with the same scope-root violation
meaning. Place it between S218 and S300 so the existing C220/S220
cross-reference and JAIL_SCOPE_CODES documentation are complete.

In `@shell/src/code/functions/info.rs`:
- Around line 156-160: Update the resolver root initialization around
primary_root and the AccessMode selection to handle an empty resolver.roots()
collection when resolver.unjailed() is true. Avoid indexing base_paths[0] in
that case, while preserving the existing primary-root behavior whenever roots
are available and the jailed-mode behavior remains unchanged.

In `@shell/src/main.rs`:
- Around line 602-603: Update the `shell::fs::grep` contract description near
the `code` and `message` fields to document S211 for missing, denied, protected,
and unjailed glob-matched paths, while preserving the existing S217 and S215
descriptions.

---

Outside diff comments:
In `@shell/src/fs/host.rs`:
- Around line 545-584: Move the denylist loop in confine_scope_root to
immediately after canonicalization and before the canon.is_dir() check, so
denylisted paths consistently return FsError::not_found_or_denied (S211) whether
the target exists or is missing. Preserve the existing S212 response for
non-denylisted paths that are not directories.

---

Nitpick comments:
In `@shell/src/code/error.rs`:
- Around line 261-278: Rename the tests
io_for_path_already_exists_maps_to_c217_with_path_prefix and
io_already_exists_maps_to_c217 to use c213, matching the assertions and current
AlreadyExists error code; leave their test logic unchanged.

In `@shell/src/code/functions/read_file.rs`:
- Line 1496: Rename the outdated test functions to match their current error
codes: in shell/src/code/functions/read_file.rs at lines 1496-1496, 2087-2087,
and 2189-2189, change c213 to c218 in the named tests; in
shell/src/code/functions/move_file.rs at line 699, change
overwrite_false_dst_exists_c217 to overwrite_false_dst_exists_c213. No assertion
or implementation changes are needed.

In `@shell/src/code/path.rs`:
- Around line 1204-1205: Rename the three
tests—resolve_in_absolute_inside_root_but_outside_scope_root_rejected_c218_naming_session,
resolve_in_absolute_in_other_root_outside_scope_root_is_c218, and
resolve_in_dotdot_within_root_but_outside_base_is_c218—to use the C220 suffix,
matching their err.code() assertions. Do not change test behavior or assertions.

In `@shell/src/fs/host.rs`:
- Around line 453-482: Extract the uncontained-path glob fallback from
path_is_non_accessible into a shared crate-internal fs utility, then update both
path_is_non_accessible and the coder path resolver to use it. Preserve
root-relative matching for contained paths and absolute-form matching only when
no configured root contains the path, including the existing slash normalization
and empty-path handling.
🪄 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: CHILL

Plan: Pro

Run ID: b2eb8b66-9fa5-4b6d-9ec3-317868f50a35

📥 Commits

Reviewing files that changed from the base of the PR and between cd1b4a8 and 075d599.

⛔ Files ignored due to path filters (2)
  • approval-gate/Cargo.lock is excluded by !**/*.lock
  • shell/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (39)
  • approval-gate/Cargo.toml
  • approval-gate/README.md
  • approval-gate/src/filesystem_access.rs
  • approval-gate/src/types.rs
  • approval-gate/tests/golden/schemas/approval.get-pending.json
  • approval-gate/tests/golden/schemas/approval.list-pending.json
  • approval-gate/tests/golden/schemas/approval.pending-created.json
  • shell/CHANGELOG.md
  • shell/Cargo.toml
  • shell/README.md
  • shell/src/code/config.rs
  • shell/src/code/error.rs
  • shell/src/code/functions/create_file.rs
  • shell/src/code/functions/info.rs
  • shell/src/code/functions/mod.rs
  • shell/src/code/functions/move_file.rs
  • shell/src/code/functions/read_file.rs
  • shell/src/code/path.rs
  • shell/src/config.rs
  • shell/src/configuration.rs
  • shell/src/exec/policy.rs
  • shell/src/fs/error.rs
  • shell/src/fs/host.rs
  • shell/src/functions/workspace.rs
  • shell/src/main.rs
  • shell/tests/code_golden_errors.rs
  • shell/tests/code_path_jail.rs
  • shell/tests/code_unified_protection.rs
  • shell/tests/e2e/workers/harness/src/cases-fs-host-jail.ts
  • shell/tests/e2e/workers/harness/src/cases-fs-protocol-break.ts
  • shell/tests/features/coder/create_file.feature
  • shell/tests/features/coder/move.feature
  • shell/tests/features/coder/path_security.feature
  • shell/tests/features/coder/read_file.feature
  • shell/tests/golden/errors.json
  • shell/tests/golden/schemas/coder.create-file.json
  • shell/tests/golden/schemas/coder.info.json
  • shell/tests/golden/schemas/coder.move.json
  • shell/tests/golden/schemas/coder.read-file.json

Comment thread shell/README.md
Comment on lines +189 to 198
| Code | Meaning | fs twin |
|---|---|---|
| `C210` | Malformed input: bad payload, illegal line numbers, overlapping ops. | `S210` |
| `C211` | Path not found, permission denied, matched `non_accessible_globs`, or under `fs.denylist_paths` — deliberately ONE code and wording for all four, so a caller can't probe for a denied path's existence. | `S211` |
| `C213` | `create-file`/`move` saw an existing target and `overwrite=false`. | `S213` |
| `C215` | Path escapes every allowed root, lexically or through a symlink (jailed mode only). | `S215` |
| `C216` | Underlying I/O error. | `S216` |
| `C218` | File exceeds `max_read_bytes`/`max_write_bytes`. | `S218` |
| `C220` | Path resolves inside a configured root but outside the per-call `scope_root` the session is scoped to. | `S220` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

S220 is referenced as C220's "fs twin" but never documented in the ## Errors table.

Line 197 names S220 as C220's fs twin, and approval-gate's JAIL_SCOPE_CODES (["S215", "S220", "C215", "C220"]) confirms it's a live code — but the S2xx table below (lines 256-269) jumps from S218 straight to S300 with no S220 entry, leaving the "fs twin" cross-reference dangling for readers.

📝 Suggested addition to the S2xx table
 | `S218` | `fs.max_read_bytes` / `fs.max_write_bytes` cap exceeded. |
+| `S220` | Path resolves inside the `fs.host_roots` jail but outside the per-call `cwd`/session scope. |
 | `S300` | Sandbox VM boot failed (needs a virtualization host: Apple Silicon or `/dev/kvm`). |

Also applies to: 256-269

🤖 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 `@shell/README.md` around lines 189 - 198, Add the missing S220 entry to the
README’s S2xx Errors table, documenting it as the fs twin for C220 with the same
scope-root violation meaning. Place it between S218 and S300 so the existing
C220/S220 cross-reference and JAIL_SCOPE_CODES documentation are complete.

Comment on lines +156 to +160
mode: if resolver.unjailed() {
AccessMode::Unjailed
} else {
AccessMode::Jailed
},

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 | 🔴 Critical | ⚡ Quick win

Panic when fs.host_roots is empty.

The PR objective and AccessMode::Unjailed documentation indicate that fs.host_roots can be empty in unjailed mode. If resolver.roots() is empty, the upstream assignment at line 153 (let primary_root = base_paths[0].clone();) will panic.

🐛 Proposed fix
-    let primary_root = base_paths[0].clone();
+    let primary_root = base_paths.first().cloned().unwrap_or_default();
📝 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
mode: if resolver.unjailed() {
AccessMode::Unjailed
} else {
AccessMode::Jailed
},
let primary_root = base_paths.first().cloned().unwrap_or_default();
🤖 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 `@shell/src/code/functions/info.rs` around lines 156 - 160, Update the resolver
root initialization around primary_root and the AccessMode selection to handle
an empty resolver.roots() collection when resolver.unjailed() is true. Avoid
indexing base_paths[0] in that case, while preserving the existing primary-root
behavior whenever roots are available and the jailed-mode behavior remains
unchanged.

Comment thread shell/src/main.rs
Comment on lines +602 to +603
{ code, message }; common: S217 bad regex, S215 jail escape. For token-budgeted search with \
context lines and noise filtering, prefer coder::search."

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 | 🟡 Minor | ⚡ Quick win

Document S211 redaction for shell::fs::grep.

The updated contract redacts missing, denied, and protected paths—including unjailed glob matches—as S211, but this description only advertises S217 and S215. Add the S211 cases so callers can handle expected redacted failures correctly.

🤖 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 `@shell/src/main.rs` around lines 602 - 603, Update the `shell::fs::grep`
contract description near the `code` and `message` fields to document S211 for
missing, denied, protected, and unjailed glob-matched paths, while preserving
the existing S217 and S215 descriptions.

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