fix(tools): allow autonomous sessions to write the workspace directory (#1493)#1498
Merged
Aaronontheweb merged 3 commits intoJun 25, 2026
Conversation
netclaw-dev#1493) Autonomous (non-interactive) reminder/webhook sessions were confined to a filesystem write zone of session + project only (introduced in netclaw-dev#1250), which blocked them from persisting state files under the workspaces directory — the operator's designated writable working area. An autonomous reminder that tried to write a dedup/state file there hit "autonomous session may only access files inside its session directory, project directory, or configured autonomous roots", degraded, and stalled. Two compounding causes, both fixed: - ScopedFileAccessPolicy.ResolveAutonomousZone included the global read roots for reads but excluded them for writes/attach. It now additionally includes the configured workspaces directory for write/attach access — but NOT the system-managed skills/identity trees, which stay read-only (an autonomous session must never rewrite its own identity or skills). - FileWriteTool/FileEditTool/AttachFileTool/SetWorkingDirectoryTool constructed ScopedFileAccessPolicy without NetclawPaths, leaving the global-roots cache empty. NetclawPaths is now threaded through these tools (mirroring FileReadTool/FileListTool) so the {workspaces_dir} token resolves. Cross-boundary contract: the workspaces root is sourced from NetclawPaths.WorkspacesDirectory, which the daemon builds from the Workspaces:Directory config (Program.cs), so writes land where the operator pointed them. Added a test asserting the policy honors a custom configured workspaces directory. Updates the netclaw-operations skill (scheduling reference) to describe the autonomous zone accurately, including the now-writable workspace.
…(no silent-skip fallbacks) Review follow-up to netclaw-dev#1493. The same silent-fallback pattern that hid the workspace-write bug existed across the file/shell tool surface: NetclawPaths, ToolPathPolicy, and ShellCommandPolicy were all optional/nullable and consumed with `?.` / `is not null`, so a null silently disabled the check — path roots dropped, deny-lists skipped, hard-deny command evaluation bypassed. All three are constructed unconditionally in production, so nullability bought nothing but a security-skip hazard. - ScopedFileAccessPolicy, FileReadTool, FileListTool, FileWriteTool, FileEditTool, AttachFileTool, SetWorkingDirectoryTool, ShellTool, ShellTrustZonePolicy and WithFirstPartyTools now require these dependencies (non-nullable). Consumption sites drop the `?.`/null-guards. - SkillToolRegistration uses GetRequiredService<ToolPathPolicy>() instead of the nullable GetService (ToolPathPolicy is a registered singleton). - Feature-toggle params that genuinely mean "feature off" (searchBackend, webhookRouteStore) stay optional. - Call sites (production + ~40 test sites) updated to pass explicit dependencies; the compiler enforced completeness. Adds an AGENTS.md (constitution) rule: optional/nullable parameters are rare by default — never added for compatibility, and especially not for security- relevant dependencies where a null silently disables the check. Note: ToolAccessPolicy (tool-exposure filtering in SearchToolsTool/LoadToolTool) is the same pattern and a good follow-up; left out here to keep this change scoped to the file/shell access surface. Full Netclaw.Actors.Tests suite green (2479); solution builds clean.
Aaronontheweb
commented
Jun 25, 2026
Aaronontheweb
left a comment
Collaborator
Author
There was a problem hiding this comment.
the rest of this PR is essentially just making the file resolution policy non-optional inside tool calls.
| if (accessKind == AccessKind.Read) | ||
| roots.AddRange(_cachedGlobalReadRoots.Value); | ||
| else if (_cachedWorkspacesRoot.Value is { } workspacesRoot) | ||
| roots.Add(workspacesRoot); |
Collaborator
Author
There was a problem hiding this comment.
LGTM - solves the issue inside the policy class.
This was referenced Jun 26, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes #1493. Autonomous (non-interactive) reminder/webhook sessions could not write files under
{workspaces_dir}— the operator's designated writable working area — even though the workspace exists to be written to. An autonomous reminder that tried to persist a dedup/state file there hit:…then degraded and stalled (a root trigger for the #1492 duplicate-guard hang).
Root cause (two compounding issues)
ScopedFileAccessPolicy.ResolveAutonomousZoneincluded the global read roots for reads but excluded them for writes/attach (introduced in fix(tools): unblock non-interactive shell + webhook provenance + autonomous filesystem zone (#1244) #1250). So autonomous writes were confined to session + project only.FileWriteTool/FileEditTool/AttachFileTool/SetWorkingDirectoryToolconstructedScopedFileAccessPolicywithoutNetclawPaths, leaving the global-roots cache empty — so even read-eligible operations on those tools couldn't resolve{workspaces_dir}.Fix
NetclawPathsis threaded into the four write-side tools (mirroringFileReadTool/FileListTool), so{workspaces_dir}resolves.The workspaces root is sourced from
NetclawPaths.WorkspacesDirectory, which the daemon builds from theWorkspaces:Directoryconfig (Program.cs:351) — verified, so writes land where the operator pointed them.Tests
AutonomousZoneClampTests: replaced the old "can read but not write workspace" assertion withAutonomous_personal_can_write_workspaces_but_not_identity_or_skills(workspace writable; identity/skills denied) and addedAutonomous_personal_can_write_under_configured_custom_workspaces_dir(cross-boundary contract — honors the configured override).Netclaw.Actors.Testssuite green (2479 passed).dotnet slopwatch analyze: clean (the 3 warnings are pre-existing, in files this PR doesn't touch); copyright headers verified.Skill
Updated
netclaw-operations(scheduling reference) to describe the autonomous zone accurately, including the now-writable workspace; bumpedmetadata.version2.18.0 → 2.19.0.Gates not run locally
The behavioral eval suite (
./evals/run-evals.sh) requires a live model endpoint not configured in this environment — should be run in CI / before merge given the skill-content edit.