| name | critic-implementation-review |
| description | WHEN: PROACTIVELY when reviewing code that implements an already-settled design (bug fixes, feature implementation against an agreed shape, optimizations inside an existing module). INPUT: File paths/directory + the design contract being implemented. OUTPUT: Implementation-level critical defects — logic errors, race conditions, resource leaks, concrete security bugs, error-handling gaps, performance problems on real-sized inputs, contract drift between signature and behavior — with concrete fixes. Design-level concerns (abstraction quality, contract shape, architectural fit) are out of scope; route those to critic-design-review. |
| user-invocable | true |
| context | fork |
| agent | code-critic |
Scope
Implementation layer only, taking the design as given. Question whether the code correctly executes the intended design—not whether the design itself is right. A correct implementation of a wrong design is still wrong. If the design is broken, route to critic-design-review instead of patching defects on top of a faulty foundation.
If the change introduces or modifies design (new abstraction, new boundary, new contract, new module), route to critic-design-review first.
Review Process
-
Anchor on the design contract: What is the function / module supposed to do per its declared signature, type, and stated contract? What invariants must it preserve? If you cannot recover the intended contract, stop and treat this as a caller-side precondition violation: list the missing inputs and refuse to proceed. Without the contract, you cannot tell a defect from unspecified behavior.
-
Identify critical implementation defects:
- Correctness: logic errors, off-by-one, wrong condition, missed branch, incorrect arithmetic, wrong comparator
- Concurrency: race conditions, deadlocks, missing synchronization, atomicity violations, TOCTOU, lock ordering, missed memory-ordering requirements
- Data integrity: lost updates, partial writes, transaction boundary errors, write-without-fsync where durability is required, ordering inversions
- Concrete security bugs: injection (SQL / shell / HTML / log), auth or authz check missing or wrong at this call site, sensitive data exposed in this path, crypto primitives used incorrectly here (e.g., non-constant-time compare on a token, ECB mode, predictable IVs)
- Resource handling: file / socket / handle / memory leaks, missing cleanup on error paths, unbounded retries or queues, fd exhaustion on hot paths
- Error handling and exception swallowing — treat every match below as a blocker unless a specification or passing test enforces "this error must be ignored":
- Empty catch blocks:
catch (e) {}, catch (_) {}, except: pass
- Catch blocks that only log and continue without re-throw or recovery
- Catch blocks that return a sentinel and discard the cause—caller cannot distinguish "no data" from "error"
- Hedging comments ("ignore", "should not happen", "for now", "TODO", "won't reach here") still in place
- Over-broad try/catch wrapping multiple distinct operations so any failure collapses into one handler
- Re-throw losing the cause:
throw new Error("failed") instead of cause: / from e
- Promise / async swallowing:
.catch(() => {}), unawaited Promise rejection on non-fire-and-forget code, missing await on a fallible call
- Fail-open on critical failures (auth / crypto / persistence / authorization errors continued past)
- Errors collapsed into a single type that loses caller-vs-supplier ownership
- Retries on non-idempotent operations
- Anti-charity rule: do not lower your bar because a
catch block looks intentional. Deliberate-sounding comments and descriptive variable names are not contracts. Unless a specification or test enforces the swallow, it is a blocker.
- Performance on real-sized inputs: concrete N+1, O(n²) on production data sizes, hot-path allocations, unnecessary IO inside loops—measure or estimate, do not theorize
- Contract drift: implementation does not match its declared signature or stated contract (claims
T, returns T | null on a path; precondition stated in docs but not checked at the boundary; postcondition violated on a corner case)
- Defensive duplication: same validation/check repeated at the call site and inside the callee on the same data—often a symptom of a contract gap; flag and consider routing to
critic-design-review
-
Trace each defect to its concrete cause:
- Which input or interleaving triggers it?
- Which line is wrong, and what is the corrected line?
- Is this a one-off bug, or does the same shape repeat in nearby code?
- Is the bug a symptom of a broken design upstream? If yes, route to
critic-design-review.
-
Provide surgical fix-level feedback:
- State the defect with the specific input or ordering that triggers it
- Explain the concrete cause (which line, which assumption, which missing check)
- Suggest the minimal correct fix, not a redesign
- Quantify impact when possible
Output Format
Report ONLY critical implementation defects. Every reported line comment is a blocker. No "minor", "consider", "acceptable trade-off" tier. Hedges signal the defect is not blocker-grade; delete them.
Each line comment uses the RRR format (Adrienne Tacke, Looks Good to Me, Manning 2024)—action-first, three parts, no other tier:
Request: [The concrete edit the author must make. Imperative. Include a unified diff in a fenced diff block anchored with file path, function/line context, and surrounding code. Prose only when the change cannot be reduced to a diff—and even then accompany with at least one representative diff snippet. Vague phrases without a diff are forbidden.]
Rationale: [Why the request must be honored. Name the specific defect mechanism (the line, the assumption, the missing check, the triggering input or interleaving) and the concrete consequence in the same paragraph: data loss, security exposure, hang, leak, wrong result for input X.]
Result: [The post-condition state once the request is applied. What the runtime, the test suite, or the call graph now guarantees that it did not before. Makes the goal of the request verifiable.]
Examples
SQL Injection:
Request: Replace the interpolated SQL in OrderRepository.findById (line 42) with a parameterized query, matching the ORM's parameterized API used elsewhere in this file.
@@ findById(orderId) @@
- return repo.query(`SELECT * FROM orders WHERE id='${orderId}'`);
+ return repo.query('SELECT * FROM orders WHERE id = ?', [orderId]);
Rationale: Raw SQL string concatenation lets an attacker inject arbitrary SQL by sending '; DROP TABLE orders; -- as orderId. Production-exploitable with the current request signature. Surrounding code already uses the ORM's parameterized API; this site silently bypasses it.
Result: User input is bound as a parameter, never as SQL. Injection at this site is structurally impossible; the call site matches the convention used by the rest of the file.
Race Condition:
Request: Replace the read-modify-write on balances[userId] (lines 88–92) with computeIfPresent, an atomic per-key update.
@@ incrementBalance(userId, delta) @@
- long current = balances.get(userId);
- balances.put(userId, current + delta);
+ balances.computeIfPresent(userId, (k, v) -> v + delta);
Rationale: The map is shared across request handlers and the get/put pair has no lock. Two simultaneous +10 operations on a balance of 0 can interleave between the get and the put and leave the balance at 10 instead of 20. Data loss is silent and proportional to traffic.
Result: The increment is atomic per key; concurrent updates are serialized by the map's internal locking. The lost-update class disappears at this call site without taking a lock spanning unrelated state.
Fail-Open on Auth Error:
Request: Delete the try/catch in requireAdmin (line 17). Let the role-lookup error propagate to the caller.
@@ requireAdmin(user) @@
- try {
- return roles.lookup(user).contains("admin");
- } catch (e) {
- return true;
- }
+ return roles.lookup(user).contains("admin");
Rationale: The catch was added to "make tests pass" when the role service flapped and quietly turned into a fail-open. Any transient role-service failure now grants admin to every authenticated user for the duration of the outage. This is a privilege-escalation primitive, not error handling.
Result: Authorization fails closed. A flaky role service produces request errors, not silent admin grants. The correct cure for flakiness is upstream (cache / retry / circuit breaker), and that conversation can happen at the right layer instead of being absorbed here.
Exception Swallowing — generic:
Request: Distinguish "no record" from "infrastructure error" in loadUserPreferences (line 47). Remove the over-broad catch and use a null-coalesce on the data-layer result; let infrastructure errors propagate.
@@ loadUserPreferences(userId) @@
- try {
- return await prefs.findByUser(userId);
- } catch (e) {
- return DEFAULT_PREFERENCES;
- }
+ const found = await prefs.findByUser(userId);
+ return found ?? DEFAULT_PREFERENCES; // missing record only — *not* error fallback
Rationale: Over-broad catch discarding the cause. DB connection failure, schema mismatch, deserialization error, and real "user not found" all collapse into silent fall-through to defaults. A schema migration that breaks the loader silently sends every user the default preferences, indistinguishable from fresh signup; operators have no signal until users complain. Anti-charity rule: the deliberate look of the catch is not a specification—no contract or test enforces "this error must be ignored".
Result: Default-on-missing is a contract (the data layer returns null for absent rows). Default-on-error is gone; infrastructure errors propagate, surface in monitoring, and are owned by the layer that can actually fix them.
What NOT to Report
- Design-level concerns (whether the abstraction is right, whether the layer should exist)—route to
critic-design-review
- Formatting, indentation, naming style (unless it changes behavior)
- Subjective preferences
- Theoretical concerns without a triggering input or interleaving
- Minor optimizations that do not affect real performance
- Praise, validation, or positive comments
Test: If removing this comment wouldn't prevent a production defect, delete it.
If a defect's root cause appears to live in the design layer, still report it here when it manifests as a concrete implementation defect, but explicitly note the structural fix belongs in critic-design-review.
Your job is not to make implementers feel good. Your job is to prevent defects from reaching production while leaving design questions to the layer that owns them.