JSON-RPC fs.read_file fails #74
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_shrimp#74
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The path exists, it can't be accessed
Repro:
Implementation Spec for Issue #74 — JSON-RPC
fs.read_filefailsObjective
Make file chips for crew/agent-created files open successfully. Today, clicking a file the crew agents produced fails with a "path can't be accessed" error even though the file exists, because the relative path is sent to
fs.read_file(which resolves against the global workspace /$HOME) instead ofjob.file(which resolves inside the owning job's sandbox).Root Cause (verified in code)
context.workspace_dirthat is the job's sandbox subdirectory, not the daemon's globalconfig.workspace_dir. "Wrote file" cards emitabs_path = context.workspace_dir.join(path)(crates/hero_shrimp_engine/src/execution/executor/mod.rs:104-113).LinkedText(crates/hero_shrimp_web/ui/src/components/LinkedText.tsx). The crew form is a backtick-fenced relative path; the click handler callssetOpenFile(path)with no job id — it does not use the existingopenJobFile(jobId, path)helper.openFileJobId()is null,FileModal.loadFile(FileModal.tsx:22-32) skips thejob.filebranch (the only branch that resolves a relative path inside the job's sandbox) and falls through tofs.read_filewith the relative path.method_fs_read_file(crates/hero_shrimp_server/src/rpc/methods/fs.rs) callscheck_read_access→resolve_user_path(fs_policy.rs:241-282) resolves a bare relative path against the globalconfig.workspace_dirthen$HOME. The crew file lives in a job sandbox dir that is neither, socanonicalizefails → "can't be accessed".This is the companion of the already-merged
fs.listfix (commit6f95dfe2), which corrected frontend method wiring but did not address job-relative file chips inLinkedText. The bug class is already documented instore.ts:440-444.Requirements
FileModalreads viajob.file(sandbox-aware) rather thanfs.read_file.fs.read_file/check_read_accessfor a relative path should also try known per-job sandbox workspace roots before failing.~/paths must keep working exactly as before.SHRIMP_FS_RESTRICT_TO_WORKSPACE).Files to Modify
crates/hero_shrimp_web/ui/src/components/LinkedText.tsx— accept optionaljobIdprop; callopenJobFile(jobId, path)instead ofsetOpenFile(path).crates/hero_shrimp_web/ui/src/store.ts— thread owning job id into the linker; confirmopenJobFileis the single entry point.LinkedTextfor job/crew message text — pass the message's job id.crates/hero_shrimp_server/src/rpc/fs_policy.rs— inresolve_user_path, probe known job sandbox workspace roots for relative paths before returning the non-existent$HOMEcandidate.crates/hero_shrimp_web/ui/src/components/FileModal.tsx— remove the hardcoded/home/driver~substitution; let the backend expand~.Implementation Plan
Step 1: Backend — resolve relative paths against job sandbox roots
Files:
crates/hero_shrimp_server/src/rpc/fs_policy.rsresolve_user_path(lines 241-282), after theworkspace_dirprobe and before the$HOMEfallback, iterate known job-sandbox roots (job_artifact_dir/job_workspace_path); return the first candidate that.exists().canonicalize+ blocklist + strict-mode checks still run.Dependencies: none
Step 2: Frontend — make
LinkedTextjob-awareFiles:
crates/hero_shrimp_web/ui/src/components/LinkedText.tsxjobId?: string | nullprop; in the path-chiponClick, callopenJobFile(props.jobId ?? null, t.value); importopenJobFilefrom../store.Dependencies: relies on existing
openJobFile(store.ts:448-451)Step 3: Frontend — pass job id from message-rendering call sites
Files: components rendering
<LinkedText>for job/crew content (enumerate via grep)jobId={...}from the surrounding message/job context; omit for non-job surfaces.Dependencies: Step 2
Step 4: Frontend — remove
/home/driverhardcode in FileModalFiles:
crates/hero_shrimp_web/ui/src/components/FileModal.tsxpath.replace(/^~/, '/home/driver'); pass~/...straight tofs.read_file(backend expands~).Dependencies: independent
Step 5: Rebuild bundled UI assets
crates/hero_shrimp_web/static/v2/assets/app.*.jsandstatic/index.htmlso the served bundle matches the TSX changes.Dependencies: Steps 2-4
Step 6: Tests
Files:
crates/hero_shrimp_server/src/rpc/fs_policy.rsinvalid_paramserror.fs_policytests still pass.Acceptance Criteria
job.file.fs.read_filewith a relative path that exists only in a job sandbox resolves; one that exists nowhere still returns the existing error.~/paths still open;~expands to the server's real$HOME(no/home/driverhardcode).fs_policytests pass plus the new test.Notes
store.ts:440-444: job-owned files fail viafs.read_fileand must usejob.file.LinkedTextis the one chat surface that never adoptedopenJobFile.Test Results
Backend (
cargo test -p hero_shrimp_server):Includes the new unit test
relative_path_resolves_against_job_sandboxinrpc::fs_policy, which asserts that a relative path present only under a job sandbox root resolves, while a relative path that exists nowhere still returns the existinginvalid_params"Cannot resolve" error. All pre-existingfs_policytests (tilde_expands_to_home,relative_paths_resolve_against_home,plain_existing_file_allowed_in_default_mode,rejects_home_ssh_subpath,strict_mode_rejects_outside_workspace) still pass.Frontend:
cargo build -p hero_shrimp_websucceeds;tsc --noEmitreports no new type errors in the touched files;vite buildregenerated the bundled assets (static/assets/app.D1-tO6Ad.js,static/index.html).Summary
Fixes
fs.read_filefailing for crew/agent-created files. Root cause: those files live in a per-job sandbox (<workspace>/jobs/<job_id>/...), but the clickable file chips in chat/crew text were callingfs.read_filewith a bare relative path and no job id.fs.read_fileonly resolved relative paths against the globalworkspace_dirand$HOME, so the file "existed but couldn't be accessed."Changes
Backend:
crates/hero_shrimp_server/src/rpc/fs_policy.rs-resolve_user_pathnow probes each live job's sandbox workspace root for a relative path (after the globalworkspace_dirprobe, before the$HOMEfallback). Existence-gated, so it never changes resolution for paths that already resolve. Added a unit test covering the job-sandbox hit and the still-failing nowhere case.Frontend (file chips are now job-aware, routing through
job.filewhich resolves inside the owning job's sandbox):crates/hero_shrimp_web/ui/src/components/LinkedText.tsx- accepts an optionaljobIdprop; path chips now callopenJobFile(jobId, path)instead ofsetOpenFile(path).crates/hero_shrimp_web/ui/src/components/Markdown.tsx- threads an optionaljobIdthroughInlineRendertoLinkedTextand the inline-code path chip.crates/hero_shrimp_web/ui/src/components/MessageBody.tsx- forwardsjobIdtoMarkdown.crates/hero_shrimp_web/ui/src/components/ChatThread.tsx- passes the message'sjobIdtoMessageBody(main body and job-segment "say").crates/hero_shrimp_web/ui/src/components/CrewPage.tsx- passesm.job_idto the crew-messageLinkedText.crates/hero_shrimp_web/ui/src/components/JobDrawer.tsx- passes the job's id to the goal/errorLinkedText.crates/hero_shrimp_web/ui/src/components/FileModal.tsx- removed the hardcoded/home/driversubstitution for~; the path is passed through and the backend expands~to the server's real$HOME.The frontend job-id threading is the primary correctness fix; the backend job-sandbox probe is defense-in-depth for any chip that still arrives without a job id.
Notes
~/paths keep working viafs.read_file.SHRIMP_FS_RESTRICT_TO_WORKSPACEstrict mode are unchanged.Correction — actual root cause and revised fix
The earlier spec on this issue mis-diagnosed the cause (it assumed job-sandbox relative-path resolution). After reproducing against the real reply, here is what actually breaks, with evidence.
What actually fails
In a crew reply, deliverables are rendered as a table:
The crew panel feeds this raw text to the universal linker (
LinkedText), which linkifies paths. Tested against the live link regexes:Student_Grades.xlsx— matches no chip rule (no/) -> not clickablecreate_grades.py— matches no chip rule (no/) -> not clickable/home/rawan/hero/var/shrimp/workspace/— matches the absolute-path rule -> the only clickable chipSo the only thing the user can click is the directory.
fs.read_fileon a directory returns"... is not a file". That is why every file type fails to open — it is not specific to binaries, and the files themselves exist and are readable.Fix
Make backtick-fenced bare filenames (no directory part) clickable chips, in both the universal linker and the markdown inline-code path:
crates/hero_shrimp_web/ui/src/components/LinkedText.tsxcrates/hero_shrimp_web/ui/src/components/Markdown.tsxClicking resolves the bare filename against the workspace root server-side (existing
resolve_user_pathbehaviour, unchanged), so the actual file opens. The match is gated behind a known file-extension allowlist so ordinary code talk (array.length,obj.map) and version strings (1.2) are not linkified.No backend changes. The bundled UI assets are rebuilt.
Known caveat
A binary deliverable (e.g. the
.xlsx) now reachesfs.read_file, which still rejects non-UTF-8 content with a"... is not UTF-8"message. Text deliverables open normally. Graceful binary handling (preview/download) is tracked separately and not part of this change.