Cache/navigation binding false positives and unsafe mutation parallelization

Author: y0u-0Created Sep 14, 2026Updated Sep 14, 2026

The reproducible cases below have an implementation and regression coverage ready in this patch, at commit b0db691ed957bcd13689378555f207f0bf35833a.

Upstream pull-request creation was rejected by GitHub with y0u-0 does not have the correct permissions to execute CreatePullRequest, so I am submitting the reproductions and proposed fix here for maintainer review.

Why

Existing detectors confuse names with bindings, miss React cache arguments after the first position, flag Next.js's framework-error rethrow pattern, and recommend parallelizing explicitly mutating HTTP requests. These are framework/language cases that reproduce in standalone snippets without application-specific APIs.

For example, const read = memoize(load) with import { cache as memoize } from 'react' should still report read(1, {}); a parameter named read shadowing that cached function should not. Likewise, catch (error) { unstable_rethrow(error); } preserves Next.js control flow, and consecutive POST requests can require ordering even when their return values are independent.

What changed

  • server-cache-with-object-literal resolves React imports and immutable cached-function aliases through existing scope utilities. It ignores unrelated/shadowed factories and functions, and detects fresh objects or arrays in any argument position, reporting once per call.
  • nextjs-no-redirect-in-try-catch resolves imported API bindings, including navigation namespaces, and recognizes unstable_rethrow of the actual caught binding. Deferred, unrelated, shadowed, wrong-error, and swallowed rethrows do not silence the diagnostic.
  • server-sequential-independent-await and async-parallel reuse findSideEffect to preserve known mutation ordering. Regression cases cover POST, PUT, PATCH, DELETE, lowercase methods, and continued reporting of independent reads.
  • Extend the existing catch/try traversal with an optional framework-rethrow predicate; callers that omit it retain their current behavior. Reuse existing React/import/alias resolvers instead of adding competing detectors.
  • Add 37 regression cases, 26 verdict-bearing fuzz corpus fixtures, and a patch changeset. No rule IDs or tags change.

The behavior follows the React cache argument identity contract and the Next.js unstable_rethrow pattern.

Validation

Before changing the implementation, the four focused regression suites reported 26 failures and 136 passes against the upstream base. With the fixes, all 162 tests pass.

  • nr test: passed all 12 tasks (32,654 tests passed, including 27,156 detector tests). An initial concurrent run failed an unrelated timing-ratio assertion in no-mutate-then-set-or-return-same-reference; that test passed in isolation, and the full retry passed without test or implementation changes.
  • nr typecheck: passed across all 10 tasks.
  • nr lint: passed; the repository's adversarial fixtures produce warnings.
  • nr format:check: passed.
  • nr smoke:json-report: passed against the built CLI (schemaVersion=3, full mode).
  • FUZZ_RULE=<rule> FUZZ_ITERATIONS=200 FUZZ_SEED=42 FUZZ_STRICT=1 FUZZ_REQUIRE_FIRE=1 nr fuzz: passed separately for all four rules, including deterministic corpus verdicts and canonical liveness fixtures.
  • git diff --check: passed.
  • The commit's Vite+ staged checks passed. Its advisory React Doctor bootstrap emitted a generic regression warning; reproducing the bootstrap with pnpm dlx react-doctor@latest --version fails before execution with ERR_PNPM_TRUST_DOWNGRADE for transitive [email protected] (published CLI 0.9.13). No trust check was bypassed. The locally built CLI, explicitly scoped to packages/oxlint-plugin-react-doctor and changed files relative to the base, completed with one project and zero diagnostics.

Daytona parity has not run: DAYTONA_API_KEY is absent in the submitting environment. The separate react-doctor-evals checkout/cache is also unavailable, and the expected millionco/react-doctor-evals repository is not accessible through the authenticated CLI. No real-repository diagnostic delta or corpus-wide false-positive claim is made. The patch still needs that validation before merge.

Scope and limits

This is a bounded correctness fix, not a general proof of async independence or complete control-flow analysis. Cached wrappers are resolved through immutable local bindings; inter-file and mutable-wrapper dataflow remain outside this change. Mutation recognition inherits the existing side-effect helper's static syntax coverage, so dynamically constructed request options and arbitrary effectful helper functions are not newly classified. Catch traversal retains its existing conditional-rethrow approximation.

QA impact / blast radius

Diagnostic output changes for four existing rules. The optional catch traversal extension preserves default behavior for other callers. There are no runtime application changes, new dependencies, custom downstream rules, or automatic source rewrites.

Manual-test checklist

  • Verify real React cache aliases report fresh later-position objects and arrays while shadowed or unrelated functions stay quiet.
  • Verify a real Next.js rethrow of the caught error stays quiet while unrelated, deferred, and swallowed rethrows still report.
  • Verify ordered HTTP mutations stay quiet and independent reads still report in both await detectors.
  • Run the built CLI JSON smoke test.
  • Run and inspect upstream Daytona base/head corpus parity before merge.