Skip to content

Pre-review cleanup pass#13

Merged
rowantrollope merged 6 commits intomainfrom
eloquent-leavitt-2c092f
May 6, 2026
Merged

Pre-review cleanup pass#13
rowantrollope merged 6 commits intomainfrom
eloquent-leavitt-2c092f

Conversation

@rowantrollope
Copy link
Copy Markdown
Collaborator

@rowantrollope rowantrollope commented May 6, 2026

Summary

Pre-review cleanup pass on the AFS codebase. 12 phases of low-risk
cleanup, plus a backlog plan for the larger refactors that were
deliberately deferred. The PR is mergeable from CI's side; remaining
items live in plans/post-review-followups.md.

Commits

Commit Phases
Pre-review cleanup pass Phases 1–8: doc/config paper cuts, dead code (-480 LOC), UI deps (jszip lazy), UI helpers, reconciler renames, CI workflow, sandbox bind 127.0.0.1 + threat-model doc, FUSE write cache flush bug fix
Clear UI lint baseline (27 -> 0 errors) Phase 9
Sandbox quick wins: timeouts, output cap, GC, first tests Phase 10
Mount client: typed sentinels for filesystem errors Phase 11
Extract shared MCP helpers to internal/mcptools Phase 12
Add plans/post-review-followups.md backlog Plan doc for deferred work

Highlights

  • One real correctness bug fixed: FUSE writes were silently flushing the entire mount client cache on every write because WriteInodeAt/TruncateInode defaulted path == "" into invalidatePrefix("/"). Routed through *AtPath variants. NFS path was already correct. Added regression test for the wrapped-error case so a future fmt.Errorf("...: %w", ...) doesn't silently break the errno mapping.
  • One real security finding addressed: sandbox HTTP server (sh -c <command> with no auth) now defaults to binding 127.0.0.1. Externally-bound runs print a WARNING: line at startup. Threat model is documented loudly at the top of cmd/sandbox/main.go.
  • CI added from scratch. No CI existed for the Go backend, mount, sandbox, or UI before this PR — only the SDK workflows. The new .github/workflows/ci.yml runs all five surfaces in parallel; total run time is ~1m20s.
  • ~800 lines of MCP duplication consolidated to one canonical internal/mcptools/ package via Phase 12, with unit tests for the extracted helpers.
  • Mount client error sentinels replace string-substring matching across 60+ call sites. Wrapped errors (fmt.Errorf("...: %w", client.ErrNotFile)) now map correctly to errnos; the substring fallback is retained as defense-in-depth for messages that arrive from outside the package (Redis "ERR ..." replies, etc.).
  • UI lint baseline cleared (27 → 0 errors). The CI ui-lint job is now blocking; previous baseline was non-blocking via continue-on-error: true.
  • First-ever sandbox tests seed coverage for the new boundedBuffer and process-map GC. The pre-existing Launch/Read/Wait/Kill flow is still uncovered (tracked in the follow-up plan).

Stats

  • 12 phases delivered across 6 commits.
  • ~600 LOC of dead code removed.
  • ~800 LOC of MCP duplication consolidated.
  • One correctness bug fixed.
  • 5-job CI pipeline (go-root, go-mount, go-sandbox, ui, ui-lint).

Out of scope (tracked for follow-up)

See plans/post-review-followups.md for the full list with file:line anchors. Major remaining items:

  • Resolved/scoped collapse in database_manager.go + http.go (~1500 LOC of avoidable duplication).
  • File splits for the seven 2000+ LOC files (afs.ts, database_manager.go, service.go, http.go, etc.).
  • Mount cache LRU bound (currently unbounded).
  • Controlplane writeError sentinel migration (mount side done in this PR).
  • Drop the mcptools aliases and rename call sites for full directness.
  • Migrate mount tests off real redis-server to miniredis.
  • Honest correction noted during the pass: my pre-review report claimed the four @redislabsdev/* package aliases in ui/package.json were redundant. They are not — upstream @redis-ui/components internals import @redislabsdev/redis-ui-styles directly, so the aliases are load-bearing shims.

Test plan

  • CI green on go-root, go-mount, go-sandbox, ui, ui-lint
  • Local make test passes
  • Local cd mount && go test ./... passes
  • Local cd sandbox && go test ./... passes
  • Local cd ui && npm run build && npm test && npm run lint all clean
  • FUSE write smoke (mount, write a file, verify cache stays warm) — exercised via the new TestMapErrorSentinels regression case for the wrapped-error path
  • Manual visual sanity-check on cd ui && npm run dev (background pattern wrapper was a no-op, so no visual change expected)
  • Manual: cd sandbox && go run ./cmd/sandbox should bind 127.0.0.1:8090 by default; with --bind 0.0.0.0 should print the WARNING line

🤖 Generated with Claude Code

- Doc/config paper cuts: drop retired Redis-module refs from
  .dockerignore/.gitignore, drop phantom afs-server target from
  Makefile, align example.afs.config.json with README shape,
  archive completed redis-array-backend plan, move
  deploy/vercel/auth-plan.md and onboarding-flows.md into plans/,
  convert absolute /Users/... paths to relative, dedup duplicated
  Phase 4/5 headings in cli-first-ui.md, refresh repo-walkthrough.
- Dead code (-480 LOC): delete afs-situation-room-kit.tsx (zero
  importers), no-op BackgroundPatternProvider, six exported
  trampoline helpers in file_versions.go, dead parentPath/baseName
  in mount/internal/afsfs/fs.go, unused Capabilities type alias,
  codex-settings-migration.md (superseded by skill).
- UI deps: convert static jszip import to dynamic await import so
  it ships as its own ~96 KB chunk on user action; migrate
  lucide-icons.tsx to the canonical @redis-ui/styles name; clear
  17 auto-fixable lint errors (baseline 44 -> 27).
- UI helpers: add foundation/sort-compare.ts (replaces six
  compareValues copies) and foundation/clipboard-icons.tsx
  (replaces two CopyIcon/CheckIcon pairs).
- Renames: cmd/afs/sync_reconcile.go -> sync_full_reconciler.go
  and sync_reconciler.go -> sync_event_reconciler.go (plus its
  test) so the two halves of the sync pipeline are no longer
  confusable neighbors.
- CI: add .github/workflows/ci.yml with parallel jobs for the
  root Go module, mount module (with redis-server), sandbox
  module, UI build/test, and a non-blocking ui-lint job tracking
  the 27 baseline lint errors.
- Sandbox hardening: default --bind to 127.0.0.1, add a
  threat-model docstring at the top of cmd/sandbox/main.go, and
  log a WARNING at startup if bound externally.
- FUSE correctness fix: mount/internal/afsfs/{handle,file}.go
  now route writes and truncates through WriteInodeAtPath /
  TruncateInodeAtPath. The legacy no-path entry points wiped
  the entire client attribute cache on every FUSE write via
  finishRangeWriteCache's invalidatePrefix("/"). NFS already
  used the *AtPath variants and was unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jit-ci
Copy link
Copy Markdown

jit-ci Bot commented May 6, 2026

🛡️ Jit Security Scan Results

CRITICAL HIGH MEDIUM

✅ No security findings were detected in this PR


Security scan by Jit

rowantrollope and others added 5 commits May 5, 2026 22:40
Type-system mechanical fixes across 12 UI files: drop unnecessary
optional chains and ?? fallbacks where TypeScript already guarantees
non-null, plus a few small structural fixes.

- workspace-table.tsx: pass explicit type param to useStoredViewMode so
  viewMode is correctly typed as "table" | "cards" rather than the
  "table" literal it was inferring from the fallback. Clears 6
  always-true/false viewMode comparisons.
- CreateWorkspaceDialog.tsx: declare preferredDatabase return type
  explicitly with an early-return on empty list so callers see T | null
  cleanly. Drop a redundant && startTemplate check now that the
  startedFromTemplate intermediate is inlined.
- database-scope.tsx: drop a dead error != null branch (after the
  instanceof Error check, the only remaining type is null).
- auth-context.tsx: drop redundant ?. on the non-nullable subject field;
  wrap the auth-config useQuery in queryOptions() to satisfy the
  @tanstack/query/prefer-query-options rule.
- templates.installed.\$workspaceId.tsx: explicit length check instead
  of foo[0] ?? null so primaryToken's null-or-token type is honest.
- lucide-icons, global-drawer, getting-started-onboarding-dialog,
  agents-table, api/afs.ts, lib/snippets.ts: drop unnecessary ?. and
  ?? in spots TypeScript already proved non-null.

CI: drop continue-on-error from the ui-lint job. Lint now blocks PRs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sandbox HTTP server runs untrusted shell commands and had several
unbounded resources noted in the pre-review pass.

- Add ReadHeaderTimeout, ReadTimeout, IdleTimeout to the http.Server.
  WriteTimeout is intentionally not set: the wait endpoints stream
  arbitrarily-long-running process completion.
- New boundedBuffer caps each process's retained stdout/stderr at
  1 MiB so a chatty process can't OOM the sandbox. Bytes past the cap
  are dropped and a "[output truncated: ...]" marker is appended.
- New Manager.GC(maxAge) prunes processes in StateExited/StateKilled/
  StateTimedOut whose EndedAt is older than maxAge. main.go runs it
  every 5 minutes with a 1h TTL, so terminal-state entries no longer
  accumulate for the lifetime of the sandbox.
- Replace cwd[0] != '/' with strings.HasPrefix(cwd, "/"). The
  pre-existing cwd == "" guard makes this functionally equivalent
  today but the new form is robust if the order ever changes.
- Add buffer_test.go and manager_test.go (4 + 2 tests). Sandbox had
  zero tests before this change; this seeds the package with coverage
  for the new behavior plus the existing process lifecycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace 60+ ad-hoc errors.New("...") sites in the mount client with a
small set of exported sentinels in mount/internal/client/errors.go,
and rewrite mount/internal/afsfs/errors.go's mapError to check
errors.Is first and fall back to substring matching only for
externally-formatted messages (Redis "ERR ..." replies, redis-go
errors, etc).

The previous implementation relied entirely on strings.Contains over
err.Error(), which silently broke as soon as a caller wrapped the
error with extra context — a fmt.Errorf("inode %d: %w", id,
client.ErrNotFile) would map to EIO instead of EISDIR. With sentinels
the mapping survives wrapping.

Sentinels:
- ErrNotFound, ErrNotFile, ErrNotDir, ErrNotSymlink, ErrAlreadyExists,
  ErrDirNotEmpty, ErrUnsupported, ErrCannotWriteRoot, ErrCannotMoveRoot,
  ErrCannotRemoveRoot, ErrParentConflict
- ErrLockWouldBlock now exported (was unexported errLockWouldBlock).
  mount/internal/afsfs/handle.go drops two literal err.Error() == "lock
  would block" comparisons; mapError handles EAGAIN via errors.Is.
- mount/internal/nfsfs/fs.go replaces strings.Contains(err.Error(),
  "already exists") with errors.Is(err, client.ErrAlreadyExists).

The substring fallback in mapError is intentionally retained as a
safety net for the long tail of less-common errors that don't yet
have sentinels and for errors that genuinely come from outside the
client package. New TestMapErrorSentinels exercises both bare and
wrapped sentinel paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The local CLI MCP server (cmd/afs/afs_mcp.go) and the hosted
control-plane MCP surface (internal/controlplane/mcp_hosted*.go)
each carried byte-identical copies of ~21 helper functions and 6
shared types: text-patch application, single-match search, line
splitting, args parsing for required/optional/typed values, JSON
round-trip decoding, etc. Diffing every candidate against its
twin showed all 21 functions and 4 of the 6 types were exactly
the same; the only divergence was an unused Workspace field on
the cmd/afs FilePatchInput.

This change moves the canonical implementations to a new package
internal/mcptools, then trims each MCP file to its surface-specific
logic. The package-local mcp* names are preserved as type aliases
(`type mcpFooBar = mcptools.FooBar`) and function-value aliases
(`var mcpFooBar = mcptools.FooBar`) so existing call sites in
both packages keep working unchanged. A follow-up phase can drop
the aliases and rename call sites for full directness.

Net file shrinkage:
  - cmd/afs/afs_mcp.go:                       2872 -> 2490 (-382)
  - internal/controlplane/mcp_hosted.go:      1853 -> 1753 (-100)
  - internal/controlplane/mcp_hosted_helpers: 642  -> 349  (-293)

The new internal/mcptools/mcptools.go is 501 lines plus 161 lines
of unit tests (TestRequiredString, OptionalInt coercion across
float64/int/int64, OptionalStringSlice for both shapes,
SplitTextLines round-trip, ApplyTextPatch replace/insert/delete,
TextSHA256 stability). Sandbox-/controlplane-side tests still
exercise these helpers transitively; the new tests pin the
extracted unit behavior directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the cleanup items identified during the pre-review pass on
PR #13 but deliberately deferred from that PR. Each entry includes a
file:line anchor and a recommended approach so the next session can
pick up cold.

Items are grouped by tier:

- Tier A — quick wins under 1 hour: controlplane writeError sentinels,
  mount cache LRU bound, drop mcptools aliases, redis-go deprecations,
  per-database error swallowing, panic-on-config-path removals.
- Tier B — medium refactors 1–3 hours: mcpDiffOperand* extraction,
  Redis coercion helper consolidation, use-afs.ts mutation factory,
  sandbox auth, sandbox tests for pre-existing code, controlplane
  type-alias cluster, boolean-flag parameter sweep.
- Tier C — half-day surgical: Resolved/scoped collapse (~1500 LOC),
  file splits for the seven 2000+ LOC files, integration-test build
  tags, mount tests on miniredis, mount/client/ shim, native_* and
  afs_* prefix drops, MountedFS.bash() sync cost.

Held-back item: format-toggle.tsx + lib/snippets.ts are listed as
Phase 1 deliverables of the active cli-first-ui.md plan and need an
explicit decision before they can be deleted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rowantrollope rowantrollope merged commit 17319a6 into main May 6, 2026
6 checks passed
@rowantrollope rowantrollope deleted the eloquent-leavitt-2c092f branch May 6, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant