Skip to content

Commit 16640c3

Browse files
khaliqgantclaude
andauthored
feat: FUSE mount for relayfile VFS with permission enforcement (#13)
* feat: add FUSE mount for relayfile VFS with permission enforcement Adds a FUSE-based filesystem mount backed by the relayfile HTTP API. Every syscall (open, read, write, readdir, stat) becomes an API call with a scoped Bearer token. Ignored files return ENOENT, readonly files return EPERM on write. Includes local LRU cache with TTL and WebSocket-based cache invalidation for real-time multi-agent sync. - internal/mountfuse/: fs.go, dir.go, file.go, client.go, cache.go, wsinvalidate.go - cmd/relayfile-mount: --fuse flag and --mode=fuse support - Build tag //go:build !nofuse for conditional compilation - Tests for cache and FUSE operations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: auto-create v* tag on publish to trigger release workflow publish.yml now creates both sdk-v* and v* tags. The v* tag triggers release.yml which builds Go binaries, publishes npm SDK, and pushes Docker images — matching the relay repo's auto-release pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * sync fixes * fix: address 4 review finding(s) wsinvalidate.go: Fix WebSocket endpoint path /fs/events/ws -> /fs/ws dir.go: Return fuse.FOPEN_KEEP_CACHE instead of raw open flags file.go: Remove duplicate putFile call before invalidate file.go: Add RLock/RUnlock in fillEntry for thread safety Co-Authored-By: My Senior Dev <dev@myseniordev.com> * acl fixes * fix: parse ACL permissions from file content when semantics field is empty The bulk write endpoint doesn't persist the semantics field, so ACL markers store permissions in the file content as JSON. The aclGetFile function now falls back to parsing content when semantics.permissions is empty. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * mount updates * fix core package * fix: address 12 review finding(s) wsinvalidate.go: backoff reset after stable connection, ws/wss scheme support file.go: writeGen generation counter to prevent flush/Write race condition webhooks.ts: move nextRevision() after size check to avoid wasting revisions dir.go, file.go fillEntry, wsinvalidate.go endpoint: previously resolved Co-Authored-By: My Senior Dev <dev@myseniordev.com> * publish updates * feat: add permission denial logging to mount sync client When a write is denied (403) or a file is reverted, the mount client now writes to .relay/permissions-denied.log with timestamp, action, file path, and reason. Agents can check this log for details. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: revert readonly files when agent modifies via chmod bypass When a readonly file's hash differs from the tracked hash (agent used chmod to bypass 444 and wrote to it), the sync client now fetches the original content from relayfile and overwrites the local file, restoring both content and chmod 444 permissions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update HTTP 429 test expectation to match EAGAIN mapping The mapError change correctly maps HTTP 429 to syscall.EAGAIN (retryable) instead of syscall.EIO (fatal), but the test still expected EIO. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address 14 security and reliability findings across 4 groups Group 1 - HTTP API security/ACL hardening: - Log warnings when using default dev secrets (server.go) - Add ACL rule value validation with regex patterns (acl.go) - Extend aclCheckPath for tree/query_files routes (server.go) - Document TOCTOU-safe single-snapshot ACL resolution (server.go) - Add 12 path normalization tests and 18 validation tests (acl_test.go) Group 2 - SDK/core package security: - Make webhook signature verification required by default (webhooks.ts) - Move bearer token from WebSocket URL to auth message (sync.ts) Group 3 - FUSE mount reliability: - Add inode cache size limit with eviction (fs.go) - Add WS read limit, auth error detection, max failure cap (wsinvalidate.go) - Map HTTP 429 to EAGAIN instead of EIO (client.go) - Enforce maxFileBytes limit on write buffer (file.go) Group 4 - Mount command cleanup: - Cancel derived context on FUSE exit to clean up WS (fuse_mount.go) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update sync test to match token-in-auth-message change The test expected the bearer token in the WebSocket URL query string, but commit 1f87f7b moved it to a post-open auth message for security. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: mount syncer scope parsing, ReadOnly latch, .relay exclusion, and root version - Rewrite scopeGrantsWrite to recognize manage/wildcard scopes (plane:resource:action:path segments), matching server-side scopeMatches - Remove ReadOnly OR-latch so permissions reflect fresh scope evaluation - Add conflicted entry after applyWriteDenied to prevent pullRemote override - Skip .relay directory in scanLocalFiles walk - Add version field to root package.json for CI npm version compatibility Fixes: 3000084339, 3000241814, 3000241892, 3000241978 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: fsnotify, path-aware scopes, canReadPath, agentdeny, full test coverage - fsnotify file watcher for instant local change detection (replaces polling) - Path-aware scopeMatchesPath in auth.go (fixes .env leak — token with relayfile:fs:read:/src/app.ts no longer grants read to all files) - canReadPath in mount client filters pulls by token scopes - .agentdeny command filter via shell preexec hook - Readonly revert in pushSingleFile when hash differs - Full test coverage: scope matching, read filtering, write rejection, agentdeny, fsnotify watcher - Fixed TestWriteRejectionRevertsFile to use read-only scopes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * permission test coverage * devin feedback --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7210558 commit 16640c3

35 files changed

+6197
-284
lines changed

.claude/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"mcp__relaycast__*"
5+
]
6+
}
7+
}

.github/workflows/publish.yml

Lines changed: 126 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ name: Publish Package
33
on:
44
workflow_dispatch:
55
inputs:
6+
package:
7+
description: "Package to publish"
8+
required: true
9+
type: choice
10+
options:
11+
- all
12+
- core
13+
- sdk
14+
- cli
15+
default: "all"
616
version:
717
description: "Version bump type"
818
required: true
@@ -56,6 +66,7 @@ env:
5666
NPM_CONFIG_FUND: false
5767

5868
jobs:
69+
# Build all packages once, version them, and upload
5970
build:
6071
name: Build & Version
6172
runs-on: ubuntu-latest
@@ -74,16 +85,13 @@ jobs:
7485
with:
7586
node-version: "22"
7687
cache: "npm"
77-
cache-dependency-path: packages/sdk/package-lock.json
7888
registry-url: "https://registry.npmjs.org"
7989

8090
- name: Install dependencies
81-
working-directory: packages/sdk
8291
run: npm ci
8392

84-
- name: Version bump
93+
- name: Version all packages
8594
id: bump
86-
working-directory: packages/sdk
8795
run: |
8896
CUSTOM_VERSION="${{ github.event.inputs.custom_version }}"
8997
VERSION_TYPE="${{ github.event.inputs.version }}"
@@ -109,29 +117,60 @@ jobs:
109117
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
110118
fi
111119
112-
- name: Build package
113-
working-directory: packages/sdk
114-
run: npm run build
115-
116-
- name: Typecheck package
117-
working-directory: packages/sdk
118-
run: npx tsc --noEmit
120+
# Sync all package versions and internal dependencies
121+
node -e "
122+
const fs = require('fs');
123+
const path = require('path');
124+
const version = '$NEW_VERSION';
125+
126+
const packagesDir = 'packages';
127+
for (const dir of fs.readdirSync(packagesDir)) {
128+
const pkgPath = path.join(packagesDir, dir, 'package.json');
129+
if (fs.existsSync(pkgPath)) {
130+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
131+
pkg.version = version;
132+
console.log(pkg.name + ' -> v' + version);
133+
for (const depType of ['dependencies', 'devDependencies']) {
134+
for (const dep of Object.keys(pkg[depType] || {})) {
135+
if (dep.startsWith('@relayfile/')) {
136+
pkg[depType][dep] = version;
137+
}
138+
}
139+
}
140+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
141+
}
142+
}
143+
"
144+
145+
- name: Build packages
146+
run: |
147+
npm run build --workspace=packages/core
148+
npm run build --workspace=packages/sdk
119149
120150
- name: Upload build artifacts
121151
uses: actions/upload-artifact@v4
122152
with:
123-
name: sdk-build
153+
name: build-output
124154
path: |
125-
packages/sdk/package.json
126-
packages/sdk/package-lock.json
127-
packages/sdk/dist/
128-
packages/sdk/README.md
155+
package.json
156+
packages/*/package.json
157+
packages/*/dist/
129158
retention-days: 1
130159

131-
publish:
132-
name: Publish @relayfile/sdk
160+
# Publish all packages in parallel
161+
publish-packages:
162+
name: Publish ${{ matrix.package }}
133163
needs: build
134164
runs-on: ubuntu-latest
165+
if: github.event.inputs.package == 'all'
166+
strategy:
167+
fail-fast: false
168+
max-parallel: 10
169+
matrix:
170+
package:
171+
- core
172+
- sdk
173+
- cli
135174

136175
steps:
137176
- name: Checkout code
@@ -146,59 +185,76 @@ jobs:
146185
- name: Download build artifacts
147186
uses: actions/download-artifact@v4
148187
with:
149-
name: sdk-build
150-
path: _artifacts
151-
152-
- name: Overlay build artifacts onto checkout
153-
run: cp -r _artifacts/* . && rm -rf _artifacts
188+
name: build-output
189+
path: .
154190

155191
- name: Update npm for OIDC support
156192
run: npm install -g npm@latest
157193

158-
- name: Verify build artifacts
159-
working-directory: packages/sdk
160-
run: |
161-
echo "=== Verifying dist/ contents ==="
162-
if [ ! -d "dist" ]; then
163-
echo "ERROR: dist/ directory is missing!"
164-
exit 1
165-
fi
166-
FILE_COUNT=$(find dist -name '*.js' -o -name '*.d.ts' | wc -l)
167-
echo "Found $FILE_COUNT output files in dist/"
168-
if [ "$FILE_COUNT" -lt 1 ]; then
169-
echo "ERROR: dist/ contains no .js or .d.ts files!"
170-
exit 1
171-
fi
172-
ls -la dist/
173-
echo ""
174-
echo "=== Dry run to verify package contents ==="
175-
npm pack --dry-run
176-
177194
- name: Dry run check
178195
if: github.event.inputs.dry_run == 'true'
179-
working-directory: packages/sdk
196+
working-directory: packages/${{ matrix.package }}
197+
run: |
198+
PACKAGE_NAME=$(node -p "require('./package.json').name")
199+
echo "Dry run - would publish ${PACKAGE_NAME}"
200+
npm publish --dry-run --access public --tag ${{ github.event.inputs.tag }} --ignore-scripts
201+
202+
- name: Publish to NPM
203+
if: github.event.inputs.dry_run != 'true'
204+
working-directory: packages/${{ matrix.package }}
180205
env:
181206
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
182-
NPM_TAG: ${{ github.event.inputs.tag }}
183-
NEW_VERSION: ${{ needs.build.outputs.new_version }}
207+
run: npm publish --access public --provenance --tag ${{ github.event.inputs.tag }} --ignore-scripts
208+
209+
# Publish single package (when not 'all')
210+
publish-single:
211+
name: Publish single package
212+
needs: build
213+
runs-on: ubuntu-latest
214+
if: github.event.inputs.package != 'all'
215+
216+
steps:
217+
- name: Checkout code
218+
uses: actions/checkout@v4
219+
220+
- name: Setup Node.js
221+
uses: actions/setup-node@v4
222+
with:
223+
node-version: "22"
224+
registry-url: "https://registry.npmjs.org"
225+
226+
- name: Download build artifacts
227+
uses: actions/download-artifact@v4
228+
with:
229+
name: build-output
230+
path: .
231+
232+
- name: Update npm for OIDC support
233+
run: npm install -g npm@latest
234+
235+
- name: Dry run check
236+
if: github.event.inputs.dry_run == 'true'
237+
working-directory: packages/${{ github.event.inputs.package }}
184238
run: |
185239
PACKAGE_NAME=$(node -p "require('./package.json').name")
186-
echo "Dry run - would publish ${PACKAGE_NAME}@${NEW_VERSION}"
187-
npm publish --dry-run --access public --tag "$NPM_TAG" --ignore-scripts
240+
echo "Dry run - would publish ${PACKAGE_NAME}"
241+
npm publish --dry-run --access public --tag ${{ github.event.inputs.tag }} --ignore-scripts
188242
189-
- name: Publish to npm with provenance
243+
- name: Publish to NPM
190244
if: github.event.inputs.dry_run != 'true'
191-
working-directory: packages/sdk
245+
working-directory: packages/${{ github.event.inputs.package }}
192246
env:
193247
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
194-
NPM_TAG: ${{ github.event.inputs.tag }}
195-
run: npm publish --access public --provenance --tag "$NPM_TAG" --ignore-scripts
248+
run: npm publish --access public --provenance --tag ${{ github.event.inputs.tag }} --ignore-scripts
196249

197250
create-release:
198251
name: Create Release
199-
needs: [build, publish]
252+
needs: [build, publish-packages, publish-single]
200253
runs-on: ubuntu-latest
201-
if: github.event.inputs.dry_run != 'true' && needs.publish.result == 'success'
254+
if: |
255+
always() &&
256+
github.event.inputs.dry_run != 'true' &&
257+
(needs.publish-packages.result == 'success' || needs.publish-single.result == 'success')
202258
203259
steps:
204260
- name: Checkout code
@@ -210,11 +266,8 @@ jobs:
210266
- name: Download build artifacts
211267
uses: actions/download-artifact@v4
212268
with:
213-
name: sdk-build
214-
path: _artifacts
215-
216-
- name: Overlay build artifacts onto checkout
217-
run: cp -r _artifacts/* . && rm -rf _artifacts
269+
name: build-output
270+
path: .
218271

219272
- name: Commit version bump and create tag
220273
env:
@@ -224,7 +277,7 @@ jobs:
224277
git config user.name "GitHub Actions"
225278
git config user.email "actions@github.com"
226279
227-
git add packages/sdk/package.json packages/sdk/package-lock.json
280+
git add package.json packages/*/package.json
228281
if ! git diff --staged --quiet; then
229282
BRANCH="chore/release-v${NEW_VERSION}"
230283
git checkout -b "$BRANCH"
@@ -234,7 +287,6 @@ jobs:
234287
--body "Automated version bump to v${NEW_VERSION}" \
235288
--base main --head "$BRANCH"
236289
gh pr merge "$BRANCH" --auto --squash
237-
# Wait for the squash-merge to complete on main
238290
for i in $(seq 1 30); do
239291
STATE=$(gh pr view "$BRANCH" --json state --jq '.state')
240292
if [ "$STATE" = "MERGED" ]; then
@@ -248,36 +300,40 @@ jobs:
248300
fi
249301
fi
250302
251-
# Tag on main so the tag is reachable from main's history
252303
git fetch origin main
253-
git tag -a "sdk-v${NEW_VERSION}" origin/main -m "SDK v${NEW_VERSION}"
254-
git push origin "sdk-v${NEW_VERSION}"
304+
git tag -a "v${NEW_VERSION}" origin/main -m "Release v${NEW_VERSION}"
305+
git push origin "v${NEW_VERSION}"
255306
256307
- name: Create GitHub Release
257308
uses: softprops/action-gh-release@v2
258309
with:
259-
tag_name: sdk-v${{ needs.build.outputs.new_version }}
310+
tag_name: v${{ needs.build.outputs.new_version }}
260311
name: v${{ needs.build.outputs.new_version }}
261312
body: |
262-
## @relayfile/sdk v${{ needs.build.outputs.new_version }}
313+
## relayfile v${{ needs.build.outputs.new_version }}
314+
315+
### Packages
316+
- `@relayfile/core@${{ needs.build.outputs.new_version }}`
317+
- `@relayfile/sdk@${{ needs.build.outputs.new_version }}`
318+
- `relayfile@${{ needs.build.outputs.new_version }}`
263319
264320
### Install
265321
```bash
266322
npm install @relayfile/sdk@${{ needs.build.outputs.new_version }}
323+
npm install relayfile@${{ needs.build.outputs.new_version }}
267324
```
268325
269326
### Publish Details
270327
- Dist-tag: `${{ github.event.inputs.tag }}`
271328
- Provenance: enabled via `npm publish --provenance`
272329
- Registry: `https://registry.npmjs.org`
273-
- Package: `@relayfile/sdk`
274330
generate_release_notes: true
275331
env:
276332
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
277333

278334
summary:
279335
name: Summary
280-
needs: [build, publish, create-release]
336+
needs: [build, publish-packages, publish-single, create-release]
281337
runs-on: ubuntu-latest
282338
if: always()
283339

@@ -286,22 +342,17 @@ jobs:
286342
run: |
287343
echo "## Publish Summary" >> "$GITHUB_STEP_SUMMARY"
288344
echo "" >> "$GITHUB_STEP_SUMMARY"
289-
echo "**Package**: \`@relayfile/sdk\`" >> "$GITHUB_STEP_SUMMARY"
345+
echo "**Package**: \`${{ github.event.inputs.package }}\`" >> "$GITHUB_STEP_SUMMARY"
290346
echo "**Version**: \`${{ needs.build.outputs.new_version }}\`" >> "$GITHUB_STEP_SUMMARY"
291347
echo "**NPM Tag**: \`${{ github.event.inputs.tag }}\`" >> "$GITHUB_STEP_SUMMARY"
292348
echo "**Prerelease**: \`${{ needs.build.outputs.is_prerelease }}\`" >> "$GITHUB_STEP_SUMMARY"
293349
echo "**Dry Run**: \`${{ github.event.inputs.dry_run }}\`" >> "$GITHUB_STEP_SUMMARY"
294-
echo "**Registry**: \`https://registry.npmjs.org\`" >> "$GITHUB_STEP_SUMMARY"
295350
echo "**Provenance**: \`enabled\`" >> "$GITHUB_STEP_SUMMARY"
296351
echo "" >> "$GITHUB_STEP_SUMMARY"
297352
echo "### Results" >> "$GITHUB_STEP_SUMMARY"
298353
echo "| Stage | Status |" >> "$GITHUB_STEP_SUMMARY"
299354
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
300355
echo "| Build & Version | ${{ needs.build.result == 'success' && 'SUCCESS' || 'FAILURE' }} |" >> "$GITHUB_STEP_SUMMARY"
301-
echo "| Publish | ${{ needs.publish.result == 'success' && 'SUCCESS' || (needs.publish.result == 'skipped' && 'SKIPPED' || 'FAILURE') }} |" >> "$GITHUB_STEP_SUMMARY"
356+
echo "| Publish All | ${{ needs.publish-packages.result == 'success' && 'SUCCESS' || (needs.publish-packages.result == 'skipped' && 'SKIPPED' || 'FAILURE') }} |" >> "$GITHUB_STEP_SUMMARY"
357+
echo "| Publish Single | ${{ needs.publish-single.result == 'success' && 'SUCCESS' || (needs.publish-single.result == 'skipped' && 'SKIPPED' || 'FAILURE') }} |" >> "$GITHUB_STEP_SUMMARY"
302358
echo "| Create Release | ${{ needs.create-release.result == 'success' && 'SUCCESS' || (needs.create-release.result == 'skipped' && 'SKIPPED' || 'FAILURE') }} |" >> "$GITHUB_STEP_SUMMARY"
303-
echo "" >> "$GITHUB_STEP_SUMMARY"
304-
echo "### Install" >> "$GITHUB_STEP_SUMMARY"
305-
echo "\`\`\`bash" >> "$GITHUB_STEP_SUMMARY"
306-
echo "npm install @relayfile/sdk@${{ needs.build.outputs.new_version }}" >> "$GITHUB_STEP_SUMMARY"
307-
echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY"

.msd-autofix-findings-summary.txt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
1. [MEDIUM] .github/workflows/contract.yml:35 — <!-- devin-review-comment {"id": "BUG_pr-review-job-ca1d91a9d6c74847bff18a6d13f94f7c_0001", "file_path": ".github/workfl
2-
2. [MEDIUM] packages/core/src/webhooks.ts:594 — <!-- devin-review-comment {"id": "BUG_pr-review-job-ccdef9cc24a84af5abb66d100b3ae3ca_0001", "file_path": "packages/core/
3-
3. [MEDIUM] packages/core/src/webhooks.ts:413 — <!-- devin-review-comment {"id": "BUG_pr-review-job-ccdef9cc24a84af5abb66d100b3ae3ca_0002", "file_path": "packages/core/
4-
4. [MEDIUM] packages/core/src/acl.ts:177 — <!-- devin-review-comment {"id": "BUG_pr-review-job-ccdef9cc24a84af5abb66d100b3ae3ca_0003", "file_path": "packages/core/
1+
1. [MEDIUM] internal/mountfuse/wsinvalidate.go:134 — <!-- devin-review-comment {"id": "BUG_pr-review-job-c2f61fc1a26f4c2395d9e6bdd2c66929_0001", "file_path": "internal/mount
2+
2. [MEDIUM] internal/mountfuse/dir.go:130 — <!-- devin-review-comment {"id": "BUG_pr-review-job-c2f61fc1a26f4c2395d9e6bdd2c66929_0002", "file_path": "internal/mount
3+
3. [MEDIUM] internal/mountfuse/file.go:248 — <!-- devin-review-comment {"id": "BUG_pr-review-job-c2f61fc1a26f4c2395d9e6bdd2c66929_0003", "file_path": "internal/mount
4+
4. [MEDIUM] internal/mountfuse/file.go:225 — <!-- devin-review-comment {"id": "BUG_pr-review-job-c2f61fc1a26f4c2395d9e6bdd2c66929_0004", "file_path": "internal/mount
5+
5. [MEDIUM] packages/core/src/webhooks.ts:387 — <!-- devin-review-comment {"id": "BUG_pr-review-job-0d58fd55531d4b52b5bcd4223e16c975_0002", "file_path": "packages/core/
6+
6. [MEDIUM] internal/mountfuse/wsinvalidate.go:71 — <!-- devin-review-comment {"id": "BUG_pr-review-job-0d58fd55531d4b52b5bcd4223e16c975_0003", "file_path": "internal/mount
7+
7. [MEDIUM] internal/mountfuse/dir.go:130 — ✅ **Resolved**: The current code at dir.go:130 now shows `return child, handle, fuse.FOPEN_KEEP_CACHE, 0`, which is the
8+
8. [MEDIUM] internal/mountfuse/wsinvalidate.go:134 — ✅ **Resolved**: The current code at wsinvalidate.go:134 now shows `/v1/workspaces/%s/fs/ws` which is the correct endpoin
9+
9. [MEDIUM] internal/mountfuse/file.go:248 — ✅ **Resolved**: The current code at file.go:236-247 now includes `n.mu.RLock()` and `n.mu.RUnlock()` around the field re
10+
10. [MEDIUM] internal/mountfuse/file.go:225 — ✅ **Resolved**: The current code at file.go:223-225 shows `invalidate` then `putFile` then `updateFromBuffer` — the redu
11+
11. [MEDIUM] internal/mountfuse/file.go:231 — <!-- devin-review-comment {"id": "BUG_pr-review-job-d4770554ba7b4e5aa1f6896925a9822a_0001", "file_path": "internal/mount
12+
12. [MEDIUM] internal/mountfuse/wsinvalidate.go:133 — <!-- devin-review-comment {"id": "BUG_pr-review-job-f5948a9d2b7047af850e87ae1a144d2c_0003", "file_path": "internal/mount

0 commit comments

Comments
 (0)