Production-readiness code review for Rust backend and React frontend changes
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Production-readiness code review for Rust backend and React frontend changes
allowed-tools
Read, Grep, Glob, Bash, Agent
Code Review — Production Readiness
You are a senior code reviewer for the personas-desktop Tauri app (Rust + React/TypeScript). Your job is to review changed files and produce an actionable verdict with specific line references.
Trigger
/code-review — reviews all unstaged/staged changes against master.
/code-review <file-or-glob> — reviews only matching files.
/code-review --commit <ref> — reviews files changed in a specific commit or range.
Coordination — Active-Runs Ledger
/code-review is mostly read-only but does write a review report (and may suggest fixes the user later applies). Register before writing the report; if you also apply fixes inline, register before the first fix. Per the convention in CLAUDE.md → Concurrent CLI sessions: read the file's ## Active section first; if any started-status entry overlaps the files you're about to review/fix and is <2h old, surface the conflict to the user (the other session may be actively editing the very files you're reviewing — your verdict could be stale by commit time).
Declared paths for /code-review:
The files in the review scope (resolved in Step 1 from git diff --name-only HEAD or the commit range)
.claude/code-review/REVIEW-<YYYYMMDD-HHMM>.md (or wherever the report writes)
Always: .claude/active-runs.md
If review is purely read-only and produces no file outputs (just a chat-rendered verdict), you MAY skip ledger registration — but a review on a hot codebase area still benefits from a registered "I'm reading this for review" entry so concurrent editors know their work is being graded.
At session end (after the report is written or the verdict is delivered): move your entry to the top of ## Recently completed. Update Status to completed (commit: <sha-if-fixes-applied>) or completed (review-only, no commit) or aborted (<reason>). Trim entries older than 14 days while you're there.
Never git stash other sessions' work — not even with --keep-index. If you DO apply fixes during review and your commit step needs a clean stage, use git add <path> per file (NOT git add -A / git add . / git add -u); leave everything else alone.
Use a worktree only if applying multi-file fixes. Pure read-only review can stay on the main checkout. If the user asks you to apply suggested fixes (typically multi-file), default to:
git worktree add .claude/worktrees/code-review-fix-<short-slug> -b worktree-code-review-fix-<short-slug>
cd .claude/worktrees/code-review-fix-<short-slug>
Atomic commits per fix. One commit per Severity-1 / -2 fix; bundle Severity-3 (style/nit) fixes into one polish commit. Never bundle a critical fix with a polish fix.
Verify the staged index before commit. After git add and before git commit, run git diff --cached --stat. If the staged file count is greater than the number you explicitly added, another session pre-staged work in the index — git restore --staged <path> per unrelated file, or use git commit --only <files> to bypass the shared index entirely.
Clean up the worktree after merge. Once the fix commit(s) are in git log master, from the main checkout: git worktree remove .claude/worktrees/code-review-fix-<short-slug> and git branch -D worktree-code-review-fix-<short-slug>. Treat as part of the session-end ledger ritual.
Step 1: Identify Changed Files
Determine the review scope:
# Default: all working-tree changes vs HEAD
git diff --name-only HEAD
# If user gave a commit ref:
git diff --name-only <ref>^..<ref>
Separate files into two buckets:
Rust files: src-tauri/**/*.rs
React files: src/**/*.{ts,tsx}
If no changed files match, tell the user and stop.
Step 2: Read Every Changed File
Read each file in full. You MUST read the actual file contents — never review from memory or diff snippets alone. For large files, read in sections.
Also read the diff to understand what specifically changed:
git diff HEAD -- <file>
Step 3: Rust Backend Review
For every changed .rs file, evaluate against ALL of the following. Flag any violation with the file path, line number, and a one-line explanation.
3A. Security
No unwrap/expect on user input — All external data (IPC args, DB reads, HTTP responses, file I/O) must use ?, .ok(), .unwrap_or(), or explicit match. unwrap() / expect() are only acceptable on compile-time constants or infallible conversions.
SQL injection — All queries use parameterized ? placeholders via rusqlite. No string interpolation in SQL.
Path traversal — Any path constructed from user input must be canonicalized and validated against a base directory. No raw format! into paths.
Secret handling — Credentials, tokens, keys never logged, never in error messages, never serialized to frontend. Encryption uses AES-GCM with unique nonces. Keys derived via PBKDF2 with sufficient iterations.
Command injection — No std::process::Command with unsanitized user input.
Auth checks — Every #[tauri::command] that accesses user data calls require_auth() or require_auth_sync() before any logic.
Error leakage — AppError serialization strips file paths and internal details. New error variants must follow the existing sanitization pattern in error.rs.
3B. Error Handling
Result propagation — Functions return Result<T, AppError>. No silent error swallowing (empty if let Err(_) or let _ = fallible()). If intentional, require a // deliberate: <reason> comment.
Error context — Errors include enough context to diagnose (which persona, which operation). Use .map_err() to add context when propagating.
Mutex poisoning — Mutex::lock() results are handled, not unwrapped. Pattern: .lock().map_err(|_| AppError::Internal("lock poisoned".into()))?
Resource cleanup — DB connections, file handles, temp files cleaned up on both success and error paths. Prefer RAII (Drop) over manual cleanup.
3C. Performance
No N+1 queries — Batch DB reads instead of looping single reads. Prefer SELECT ... WHERE id IN (...) over N individual selects.
Mutex hold duration — Lock is held only for the minimum critical section. No async .await while holding a sync Mutex. Use tokio::sync::Mutex if await is needed inside the lock.
Clone cost — No unnecessary .clone() on large structs. Prefer references or Arc for shared data.
Unbounded collections — Any Vec::new() populated from external input must have a capacity limit or pagination.
3D. Correctness & Best Practices
Type safety — New structs exposed to frontend derive TS, Serialize, Deserialize with #[ts(export)] and #[serde(rename_all = "camelCase")]. Verify the ts-rs binding will match frontend expectations.
Idiomatic Rust — Prefer if let over match with one arm + wildcard. Use ? over explicit match-return-err. Avoid return at end of blocks.
Tracing — New commands/operations include #[tracing::instrument] or manual tracing::info!/error! spans with structured fields.
Dead code — No unused imports, functions, or struct fields. #[allow(dead_code)] requires justification.
Concurrency — Shared state behind Arc<tokio::sync::Mutex<T>>. No Rc or RefCell in async contexts. Background tasks spawned with proper handle tracking.
Step 4: React Frontend Review
For every changed .ts/.tsx file, evaluate against ALL of the following.
Max 200 lines per component file — Count total lines (including imports and types). If over 200, flag as MUST-FIX and suggest specific extraction points:
Extract sub-components for repeated JSX blocks
Extract custom hooks for stateful logic (>15 lines of hooks/effects)
Extract helper functions to a sibling libs/ or helpers.ts file
Extract types/interfaces to a sibling types.ts file
Single responsibility — Each component does one thing. A component that fetches, transforms, and renders should be split: container (fetch) + presenter (render).
Flat JSX — Max 5 levels of nesting in returned JSX. Extract nested blocks into named components.
4B. Bug Prevention
Dependency arrays — Every useEffect, useMemo, useCallback has correct deps. No missing deps that cause stale closures. No unnecessary deps that cause infinite loops.
Null safety — Optional chaining (?.) on all potentially undefined chains. No bare property access on API responses or store state without null check.
Key props — Every .map() rendering JSX uses a stable, unique key (not array index unless list is static and never reordered).
Event handler closures — No inline () => setState(...) in .map() loops that create N closures per render. Use useCallback or extract handler with item ID.
Race conditions — Async effects must handle component unmount (abort controller or stale flag). Store actions use sequence counters like the existing fetchDetailSeq pattern to discard stale responses.
Type safety — No any type. No as casts that could fail at runtime. Prefer type guards or discriminated unions.
4C. Performance
Unnecessary re-renders — Components receiving objects/arrays as props should use useMemo for computed values. Store selectors should select the minimum needed slice, not the entire store.
Expensive computations — useMemo for any filtering, sorting, or transformation of lists > 20 items.
Bundle size — No new large dependency imports without justification. Prefer tree-shakeable imports (import { X } from 'lib' not import lib from 'lib').
Lazy loading — New top-level tabs/pages use React.lazy() + Suspense.
Selector granularity — Components use usePersonaStore(s => s.specificField) not usePersonaStore().
Step 5: Cross-Cutting Concerns
Rust ↔ TS type sync — If a Rust struct changed, verify the corresponding src/lib/bindings/<Type>.ts matches (or will after ts-rs regeneration). Flag mismatches.
Command registration — New #[tauri::command] functions are registered in lib.rsinvoke_handler.
Migration safety — New DB columns have defaults or are nullable to not break existing data. Destructive migrations (DROP, column removal) flagged as HIGH RISK.
Step 6: 5-Axis Review
Score the changes across five independent axes. Each axis gets a verdict
(PASS / FLAG / FAIL) and its own findings list. This structure ensures
no category is skipped even when one axis dominates.