Skip to content

Session Log: Consumer Product — Lint Error Remediation

This is a complete session log from a real Ouro Loop session on a consumer iOS/web product. The agent was tasked with eliminating all ESLint errors to establish a clean lint baseline. Despite being a simple task (2 files, no DANGER ZONE), the ROOT_CAUSE verification gate caught a lazy fix attempt and pushed the agent toward a genuinely superior solution pattern, demonstrating that even straightforward tasks benefit from structured verification.


Context

Project type Next.js 15 + React 19 + TypeScript consumer app
Task Eliminate all ESLint errors to establish clean lint baseline
BOUND interaction None (no DANGER ZONE touched)
Complexity Simple (config + 2 files)

BOUND (from CLAUDE.md)

DANGER ZONES: src/utils/core-engine.ts, src/app/api/chat/route.ts
NEVER DO: Never remove rate limiter, never expose API keys client-side
IRON LAWS:
  - Build must pass: `next build` with zero errors
  - All UI components must work on mobile (min 375px)
  - Core engine calculations must be deterministic

MAP

User expects:    `npm run lint` passes with zero errors
Failure modes:   overly strict config breaks workflow, fixes introduce bugs
Tightest constraint: build must still pass (IRON LAW)
Dependencies:    ESLint + React Compiler + Next.js built-in rules
Reusable:        Next.js default ESLint config already present
Success metric:  0 lint errors (warnings acceptable)

Baseline

$ npx eslint .
3 errors, 3 warnings

Errors:
1. privacy/page.tsx:14   <a> should be <Link> (next/no-html-link-for-pages)
2. privacy/page.tsx:182  <a> should be <Link> (next/no-html-link-for-pages)
3. ReadingPage.tsx:78    setState in useEffect (react-hooks/set-state-in-effect)

File:   src/app/privacy/page.tsx
Change: import Link from "next/link", replace 2 <a href="/"> with <Link href="/">
Risk:   None — server component, Link works in both server and client

Straightforward fix. No DANGER ZONE involvement.


BUILD — Fix 2: Eliminate setState in useEffect (Attempt 1)

File:   src/components/ReadingPage.tsx
Change: Extract detection logic to a derived const, keep useEffect only for
        setting the flag when the derived value becomes true

VERIFY → FAIL

$ npx eslint .
ReadingPage.tsx:81  error  setState synchronously within an effect

The React Compiler's set-state-in-effect rule is stricter than expected. Even wrapping setState in a conditional inside useEffect still triggers it.

[REMEDIATED] gate=ROOT_CAUSE action=revert_and_retry
  was: rewrote the effect but still called setState inside it
  did: realized the rule prohibits ANY setState in effect body
  now: eliminating the effect entirely — pure derived state
  bound: not in DANGER ZONE, safe to experiment

BUILD — Fix 2: Eliminate setState in useEffect (Attempt 2)

File:   src/components/ReadingPage.tsx
Change: Replace useState + useEffect pattern with pure derived value:

BEFORE:
  const [readingComplete, setReadingComplete] = useState(false);
  useEffect(() => {
    if (!readingComplete && !isLoading && messages.length > 1) {
      if (messages.some(m => m.role === "assistant")) {
        setReadingComplete(true);  // ← React Compiler rejects this
      }
    }
  }, [isLoading, messages, readingComplete]);

AFTER:
  const readingComplete =
    readingCompleteOverride ||
    (!isLoading && messages.length > 1 &&
     messages.some(m => m.role === "assistant"));
  // No effect. No setState. Pure derivation.

VERIFY → PASS

$ npx eslint .
0 errors, 3 warnings

$ npx next build
✓ Build successful

Results

  phase:        1/1
  verdict:      PASS
  bound_check:  PASS — no DANGER ZONE touched, build passes (IRON LAW)
  lint:         0 errors (was 3), 3 warnings (unchanged)
  remediation:  1 — ROOT_CAUSE gate caught symptom-level fix, forced deeper solution

LOOP — What Fed Back Into BOUND

IRON LAWS (considered adding):
  - `npm run lint` must pass with zero errors before commit

Decision: Not added yet — team should discuss whether warnings
should also block. Flagged for next review.

Methodology Observations

  1. ROOT_CAUSE gate caught a lazy fix — the first attempt "fixed" the lint error by restructuring the effect but keeping setState. The gate correctly identified this as symptom-patching. The real fix was eliminating the effect entirely.

  2. The derived state pattern was superior — not just lint-clean, but architecturally better. React's mental model prefers computed values over synchronized state. The methodology pushed toward a genuinely better solution, not just a passing test.

  3. Simple complexity was correct — the task touched 2 files, no DANGER ZONE, clear scope. No phase plan needed. The complexity router in program.md correctly identified this as "execute directly."

  4. IRON LAW verification caught nothing — but running next build after every change gave confidence. The IRON LAW "build must pass" served as a safety net even for a simple task.