Repository navigation
fix(pixel): route a new project's rooted /<name>/<file> write like <name>/<file>; the generic refusal says "Not written." - #6765
Open
Lightheartdevs wants to merge 3 commits into
Open
Lightheartdevs wants to merge 3 commits into
Lightheartdevs wants to merge 3 commits into
Conversation
…ame>/<file>, and say "Not written." on the generic refusal Open-prompt sweep on build bfb62a2, prompt 04 "make me a snake game" (Qwen3.6-35B-A3B INT4): the first write was /snake-game/index.html (7285 chars). relative() strips only the configured root, /workspace/ and ./, #6746 reads only a first segment spelled playground, and #6751's suggestion strips one leading / only when a single file name remains, so the write got the generic correction. That text did not say the file was not written, the model published "snake-game" (refused: no index.html), read /snake-game/index.html (outside the sandbox root) and snake-game/index.html, and the fourth consecutive failure stopped the run with nothing saved. Prompt 03 hit the same refusal with /bean-there/index.html and recovered only after generating the whole page again. playground-projects.mjs: - rootedProjectPath reads a rooted /<name>/<file...> as the workspace-relative <name>/<file...> only when <name> is a descriptive project folder (descriptiveFolder), not a host, container or notebook top-level directory (home, etc, tmp, usr, var, opt, Users, Library, Volumes, workspace, data, content and similar), does not match /playground/i (#6746 owns those spellings), is not a component of the configured workspace path, and no entry with that name exists at the filesystem root or in the workspace (an entry that cannot be checked counts as present). An existing workspace folder may be an owner project, so a rooted spelling is never read onto it. - A fresh-project first write uses that reading when the path is not otherwise workspace-relative and the model did not read that rooted path. It then routes exactly like <name>/<file...>: Playground/<name> is reserved (with the usual -2 suffix on collision) and the write is rewritten into it, so the follow-up preview "<name>" maps to the same folder. - After binding, a rooted /<source>... spelling of the bound project routes like <source>... for write, edit, read, preview, exec workdir and apply_patch headers. Any other rooted path keeps today's handling. - The generic write refusal now starts with "Not written." The suggestion refusal ("Not written: ... Call write again now with path ...") is unchanged, as are the exec-before-write text and the CORRECTION text itself. Tests: new tests/playground_rooted_paths.test.mjs replays the recorded snake-game write and preview through createToolLoopGuard for sandbox and gateway hosts (saved in Playground/snake-game, preview maps, no failure charged) and the coffee-landing write (lands in bean-there-2 when Playground/bean-there exists, as the recorded retry did), and covers /home/..., /etc/passwd, /tmp/x/y.py, /src/..., /playgrounds/..., /Users/..., ../ escapes, host-root entries, configured-root components, an existing workspace folder, a read rooted path, owner-named paths and later-call scope. The generic-refusal assertion in playground_guidance.test.mjs existed to prove role, generic and multi-segment names get no suggested path; it now expects the "Not written." prefix and still rejects "Not written:", "Call write" and "with path". playground_spelling.test.mjs expects the same prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A later rooted /<source>/... spelling of the bound project routes into the project only when the model has not already read that rooted path successfully as written, the same rule the first write applies. A successful read means the path resolved outside the workspace, so it is left exact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he host-root property /workspace/ is the sandbox alias and /playground/ is #6746's Playground spelling on every host, so neither is a rooted project reading. A test host whose filesystem root holds either entry no longer fails the host-root property spuriously. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
This comes from the open-prompt sweep on build
bfb62a2bc(Qwen3.6-35B-A3B INT4). The evidence is in runstrixy-int4-bfb62a2b-sweep, folders04-snake-gameand03-coffee-landing.04-snake-game ("make me a snake game") failed with nothing saved. Session file sha256
570e57ca….write /snake-game/index.html(7285 chars)pixel_ods_workspace_preview snake-gameread /snake-game/index.htmlread snake-game/index.htmlls snake-game/03-coffee-landing hit the same refusal on
/bean-there/index.html. It recovered only by generating the whole page again asPlayground/bean-there/index.html.Why the rooted path is refused on main:
relative()strips only the configured root,/workspace/and./.playground./, and only when a single file name is left.So a rooted
/<name>/<file>gets the generic correction every time.Fix (
plugin/playground-projects.mjs)rootedProjectPath). A rooted/<name>/<file…>is read as the workspace-relative<name>/<file…>only when all of the following hold:<name>passesdescriptiveFolder: not GENERIC, not a role, test or sample name, and at least 3 letters.<name>is not a top-level host, container or notebook directory:bin boot dev etc home lib* media mnt opt proc root run sbin snap srv sys tmp usr var nix private cores net afs Users Library Applications System Volumes Network Windows ProgramData workspace(s) sandbox data content output(s).<name>does not match/playground/i. Those spellings belong to fix(pixel): route Playground misspellings of a new project instead of refusing them #6746, so/playgrounds/…stays refused.<name>is not a component of the configured workspace path, compared case-insensitively.<name>exists at the filesystem root of the workspace's volume, or in the workspace. An entry that cannot be checked (EACCES, EBUSY) counts as present. An existing workspace folder may be an owner project, so a rooted spelling is never read onto it; the write keeps today's refusal.<name>/<file…>does today:Playground/<name>is reserved, with the usual-2suffix on a collision."<name>"maps to the same folder through the existing binding./<source>…spelling of the bound project routes like<source>…for write, edit, read, preview, exec workdir andapply_patchheaders. This applies unless the model already read that rooted path as written. Any other rooted path keeps today's handling (unchanged, so core refuses it).Not written.These texts do not change:Not written: … Call write again now with path …);CORRECTIONitself.Unchanged:
ownerNamesPath) and non-project requests are still preserved and never routed.RUN_PROGRESS_LIMITSandFREE_CORRECTIONS_PER_KINDdo not change.The
Not writtenassertion inplayground_guidance.test.mjs#6751 added
assert.doesNotMatch(reason, /Not written|Call write/)to the test "role, generic and multi-segment names keep only the generic correction". At the time, "Not written" appeared only in the suggestion prefix, so the assertion proved that those names get no suggested path.That distinction is kept. The test now expects the reason to start with
Not written. For a new project …, and it still rejectsNot written:(the suggestion prefix),Call writeandwith path.playground_spelling.test.mjsalso expects the new prefix.Tests
New
tests/playground_rooted_paths.test.mjs(7 tests). The first replay drivescreateToolLoopGuardthroughbefore_tool_callandafter_tool_callagainst a real temporary workspace, for both thesandboxandgatewayhosts.Playground/snake-game/index.html, unchanged;snake-gameexecutes asPlayground/snake-game;/snake-game/index.html,snake-game/index.html) return the saved file;Playground/bean-therealready present (as in the recorded workspace),/bean-there/index.htmllands inPlayground/bean-there-2, where the recorded retry landed./snake-game/index.html,/weather-tool/main.py,/photo-renamer/src/rename.pyand\snake-game\index.htmlroute and bind exactly as their relative spellings do, both direct and wrapped.Not written., with no binding and nostate.failed:/home/…,/etc/passwd,/tmp/x/y.py,/usr/…,/var/…,/opt/…,/srv/…,/mnt/…,/media/…,/root/…,/Users/…,/Library/…,/Applications/…,/Volumes/…,/System/…,/workspaces/…,/data/…,/content/…;/src/…(src is GENERIC, so it is refused whether or not a host has/src),/app/…,/public/…,/project/…,/demo/…,/ab/…,/CON/…;/playgrounds/…,/my-playground/…,/PlaygroundX/…;../,/../,//,/./,//inside the path, and a bare/snake-gameor/snake-game/;/workspacealias and/playgroundspelling) is ever read as a new project.snake-game/keeps the refusal and its file is untouched;Before the fix, 6 of the 7 new tests fail on main. The seventh (the host-root property) holds on main too. Mutation checks: removing any one guard fails at least one test. That covers the system list, the playground exclusion, the root components, the host-root check, the workspace check, the read-as-spelled check, the later mapping, its source restriction and the patch mapping.
Full Linux pixel-agent suite (
node --test tests/*.test.mjs):319299eaa0a632581e(local merge, not pushed)Recorded-trace replay. All 210 claude-open-prompts session traces were replayed through main's and this branch's guard, each under
sandboxandgateway(420 runs), with simulated receipts. Decisions differ in 4 traces:Not written.(/workspace/.openclaw/tmp/…and a host-root-prefixed path that only the replay's temporary root leaves unstripped).Across all 210 traces, the only rooted first writes are the two above plus one
/home/…write, which stays refused.Open PRs:
git merge-treeof this head with #6743, #6747, #6748, #6750 (now merged), #6752, #6753, #6754, #6755, #6716, #6722, #6732, #6756, #6759 and #6760 finds no new conflict.playground-projects.mjsor the playground tests.tool-loop-guard.mjs(and inworkspace-preview-inspect.mjsfor fix(pixel): stop whole-file rewrites and test bisection after failed tool results (output efficiency C1-C4) #6716). They conflict in the same way with this branch.🤖 Generated with Claude Code