fix(#5460): restore permissions before removing read-only download dir#5474
Conversation
The readonly_repo enforcement step removes write permissions from all files and directories in the target repo checkout. When the extracted download directory inherits these permissions, os.RemoveAll fails with EACCES because unlinkat requires write permission on parent directories. Add forceRemoveAll helper that walks the directory tree restoring owner-write permission on directories before calling os.RemoveAll. Applied at both removal sites: the defer cleanup and the pre-extraction clear in the validation loop. Closes #5460
|
🤖 Review · ❌ Terminated · Started 4:27 PM UTC · Ended 4:49 PM UTC |
Site previewPreview: https://3d9d8a5c-site.fullsend-ai.workers.dev Commit: |
|
🤖 Finished Review · ❌ Failure · Started 4:27 PM UTC · Completed 4:49 PM UTC |
Review agent results (extracted from artifact)The review agent completed successfully and produced an approve result, but the harness crashed during post-review cleanup before it could post the approval. The crash is the exact bug this PR fixes: Review findingsOne low-severity finding (
SourceResults extracted from workflow run 29938002439, artifact |
|
/fs-fix fix the issue reported in #5474 (comment). |
|
🤖 Finished Fix · ✅ Success · Started 5:59 PM UTC · Completed 6:05 PM UTC |
Capture and log os.Chmod errors to stderr during the permission- restoration walk instead of silently ignoring them. If chmod fails for an unexpected reason (e.g. TOCTOU race), the warning gives operators context that the subsequent os.RemoveAll error alone would not provide. Addresses review feedback on #5474
🔧 Fix agent — iteration 1 (human-triggered)Addressed 1 review finding: forceRemoveAll now logs chmod failures to stderr for operator visibility, matching the file's existing warning pattern. Fixed (1):
Tests: passed Decision points
Updated by fullsend fix agent |
|
/fs-review |
|
🤖 Finished Review · ✅ Success · Started 6:31 PM UTC · Completed 6:44 PM UTC |
ReviewFindingsLow
Labels: PR labels ( Previous runReviewFindingsLow
Labels: PR fixes a bug in the harness cleanup flow (internal/cli/run.go), matching component/harness and type/bug labels from the linked issue |
|
/fs-fix - Rebase and fix the issues in #5474 (review), replying to and resolving all conversations. |
|
🤖 Finished Fix · ✅ Success · Started 6:56 PM UTC · Completed 7:04 PM UTC |
- Document the assumption that readonly_repo preserves read+execute permissions, explaining WalkDir's single-pass traversal limitation - Add os.Lstat guard before os.Chmod in the error path to verify the parent is a real directory, not a symlink (defense-in-depth) - Add inline explanation to //nolint:errcheck matching internal/sandbox/ conventions for best-effort operations Addresses review feedback on #5474
🔧 Fix agent — iteration 2 (human-triggered)Addressed all 3 review findings: documented the WalkDir single-pass assumption, added os.Lstat defense-in-depth guard before chmod in the error path, and added inline nolint explanation matching sandbox conventions. All existing tests pass. Fixed (3):
Tests: passed Updated by fullsend fix agent |
|
/fs-review |
|
🤖 Finished Review · ✅ Success · Started 7:10 PM UTC · Completed 7:23 PM UTC |
|
🤖 Finished Retro · ✅ Success · Started 8:20 PM UTC · Completed 8:43 PM UTC |
Retro: PR #5474 — fix(#5460): restore permissions before removing read-only download dirTimeline
What went wellCode agent quality was high. The initial implementation was correct — Review agent findings were appropriate. All 3 findings from review #2 (WalkDir single-pass assumption, Fix agent executed cleanly. Both fix iterations addressed all findings correctly and quickly (~8–9 min each). Autonomy signal: The human reviewer approved with zero additional findings beyond what the review agent identified. The human's role was entirely operational (triggering fix/review cycles, manually relaying crashed review results). For this class of change (well-tested Go bug fix with good coverage), the review agent demonstrated sufficient coverage. What could improveThe first review's valid output was lost due to two interacting issues, adding ~1.5 hours and ~$3.50 of wasted compute:
The root cause (permission denied on read-only dirs) is fixed by this PR. The systemic resilience issue (validation loop fragility) is tracked in #5393. No new proposalsAll improvement opportunities identified in this retro are covered by existing open issues. No new proposals are warranted. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
- Merge main to pick up forceRemoveAll (PR #5474); switch both os.RemoveAll(hostRepositoryDownloadDir) call sites (pre-clear and cleanup-after-extraction-failure) to forceRemoveAll to handle read-only directories left by sandbox enforcement. - Fix post-loop sweep iteration tracking: when the sweep validates iteration i != runCount, clear repoExtractedOK so the post-script receives empty REPO_DIR rather than a mismatched checkout. Extract postLoopValidationSweep into a testable helper returning sweepResult. - Add 6 unit tests for postLoopValidationSweep covering: latest-iter pass, earlier-iter pass (clears repoOK), none pass, TARGET_REPO_DIR always empty during sweep, and repoExtractedOK preservation logic. Addresses review feedback on #5395
- Merge main to pick up forceRemoveAll (PR #5474); switch both os.RemoveAll(hostRepositoryDownloadDir) call sites (pre-clear and cleanup-after-extraction-failure) to forceRemoveAll to handle read-only directories left by sandbox enforcement. - Fix post-loop sweep iteration tracking: when the sweep validates iteration i != runCount, clear repoExtractedOK so the post-script receives empty REPO_DIR rather than a mismatched checkout. Extract postLoopValidationSweep into a testable helper returning sweepResult. - Add 6 unit tests for postLoopValidationSweep covering: latest-iter pass, earlier-iter pass (clears repoOK), none pass, TARGET_REPO_DIR always empty during sweep, and repoExtractedOK preservation logic. Addresses review feedback on #5395
Summary
Fixes the harness cleanup failure where
os.RemoveAllfails withunlinkat ... permission deniedon directories extracted from a read-only sandbox repo. Thereadonly_repoenforcement removes write permissions from all files and directories (excluding.git/), and those permissions persist when the repo is extracted back to the host viaSafeDownload. Adds aforceRemoveAllhelper that restores owner-write permission on directories before deletion.Related Issue
Fixes #5460
Changes
forceRemoveAll()function ininternal/cli/run.gothat walks the directory tree restoringu+rwxon directories, then callsos.RemoveAllos.RemoveAll(hostRepositoryDownloadDir)at both call sites: the defer cleanup (line ~910) and the pre-extraction clear in the validation loop (line ~1391)Testing
TestForceRemoveAll_ReadOnlyTree— creates a nested directory tree, removes all write permissions (mirroringchmod a-w), verifiesos.RemoveAllfails, then verifiesforceRemoveAllsucceedsTestForceRemoveAll_AlreadyWritable— verifies normal directories are cleaned up without issuesTestForceRemoveAll_NonExistent— verifies non-existent paths return nil (matchingos.RemoveAllbehavior)go vet ./internal/cli/...passesChecklist
!for breaking changes)Closes #5460
Post-script verification
agent/5460-force-remove-readonly-dirs)98e92fa6db387b8e8eb751e687cfcc41e295821b..HEAD)