Static Analysis
Concept: Vocabulary that names a phenomenon.
Static analysis checks a program for defects by reading its code without running it; its value depends less on what it can detect than on whether anyone acts on what it reports.
Also known as: Static Code Analysis, Linting; SAST (static application security testing) for the security-focused subset
You’ve seen both halves of this. A continuous integration log carries 40 warnings that everyone scrolls past, while one compiler error blocks the merge and gets fixed in two minutes. Both messages came from the same family of tools. What separated them was where they appeared, whether people trusted them, and whether ignoring them was an option.
Understand This First
• Feedback Sensor — static analyzers are the main kind of computational sensor.
• Test — the dynamic check that static analysis complements.
What It Is
A static analyzer reads source code (or a compiled form of it), reasons about what the program could do, and reports issues: places that may or may not be real faults. The family is wide:
• Formatters rewrite layout and make no judgment about correctness.
• Style checkers and linters flag constructs a team has decided to avoid, from unused imports to an if without braces.
• Type checkers prove that values flow only where their types allow. Every type checker this book mentions is an instance of this concept.
• Bug-pattern checkers look for known mistakes, often by extending the compiler.
• Security scanners (SAST tools) search for vulnerability patterns such as unsanitized input reaching a SQL query.
Google’s 2018 account of its own tooling adds two less obvious kinds: analyzers that call production services (one checks whether an employee named in a code comment still works at the company) and analyzers that inspect build outputs, such as binary size. The word lint comes from Stephen C. Johnson’s 1978 Bell Labs checker for C programs.
Contrast this with a Test. A test runs the code on inputs you chose and observes what actually happens, but only on those inputs. An analyzer considers paths no test exercised, but it has to reason about them approximately.
That approximation isn’t a tooling weakness someone will fix. H. G. Rice proved in 1953 that every non-trivial question about what an arbitrary program does is undecidable. So every analyzer makes a trade: it misses some real bugs, reports some things that aren’t bugs, or restricts itself to questions narrow enough to answer exactly. Formal Verification sits at the end of the spectrum that refuses to miss anything within its stated property, and it pays for that in effort. Most everyday analyzers accept missing some bugs in exchange for being right when they do speak.
Why It Matters
The idea that does the most work here comes from Caitlin Sadowski and her colleagues’ report on building analysis tools at Google. They call an issue an “effective false positive” if developers did not take positive action after seeing it. The definition is about behavior, not truth. A real fault the developer didn’t understand counts as an effective false positive. A technically wrong report that developers fix anyway, because the change made the code clearer, does not. In their words: “Developers, not tool authors, will determine and act on a tool’s perceived false-positive rate.”
Google turned that idea into placement rules. A check that runs in the compiler must produce no effective false positives, because it breaks the build. A check shown in code review may produce up to 10%. (A footnote admits the number was first chosen “somewhat arbitrarily” and turned out to suit developers.)
Every review-time finding carries a “Please fix” button and a “Not useful” button. Tricorder, Google’s review-time analysis platform, tracks the ratio of the two for each analyzer and disables any analyzer whose ratio passes 10% until its authors improve it. Google treats precision as a product requirement and enforces it continuously.
The history behind those rules is a list of failures. Google first ran FindBugs nightly over its whole Java codebase in 2006 and published the results on a dashboard that few engineers visited. In a companywide fix week in May 2009, engineers reviewed 3,954 FindBugs warnings (42% of 9,473) and fixed 640 of them, 16% of those reviewed. Many findings were real but not worth fixing. A later integration that posted results in code review ended in 2011, when the review tool was replaced, for two reasons: effective false positives had cost it developers’ confidence, and per-developer settings meant teammates saw different results.
The paper’s list of reasons people ignore analyzers works as a checklist for anyone introducing one: the tool isn’t integrated into their workflow, the warning isn’t actionable, the results aren’t trustworthy, the bug doesn’t manifest in practice, the fix is too expensive or risky, or the warning isn’t understood.
The diagnostic message is part of the interface. One Error Prone check flagged too many arguments passed to a printf-like Guava function that accepted only %s. About once a week someone filed a “Not useful” bug saying the check was wrong. It never was; the callers were using other format specifiers. Rewriting the message to say the function accepts only %s stopped the complaints.
For an agent, the diagnostic text is the entire brief for the repair. A message that names the rule, the reason, and the allowed alternative gets a correct fix. A bare rule ID gets a guess.
Agents change the economics on both sides. Several of the reasons humans ignore warnings stop applying: an agent reads every line of output, doesn’t find a 600-item backlog tedious, and can draft a fix in seconds. CodeCureAgent, from Pascal Joos, Islem Bouzenia, and Michael Pradel, classifies and repairs SonarQube warnings and accepts a patch only after the project builds, the warning disappears without introducing new ones, and the tests pass. On 1,000 warnings from 106 Java projects it produced plausible fixes for 96.8%, and manual inspection of 291 cases found 86.3% correct, at about 2.9 cents and four minutes per warning. At Tencent, Xueying Du and colleagues tested hybrids of an LLM and static analysis on 433 alarms; they eliminated 94% to 98% of the false positives while keeping most real bugs. A person inspecting those alarms by hand spends 10 to 20 minutes on each.
Read those results for their shape rather than their scores. The analyzer nominates, the agent triages and repairs, and a deterministic gate decides. The same research also carries a warning. Yunpeng Xiong and Ting Zhang, comparing agent frameworks on security-scanner output, report that “aggressive FP reduction can come at the cost of suppressing true vulnerabilities.” An agent that triages well also dismisses confidently, and a dismissed true positive is quieter than any false alarm.
The other change is that analyzer rules become a way to steer agents. Alvin Sng’s account of Factory’s practice puts it directly: “Linters can encode your architecture, boundaries, and ergonomics directly into the code generation loop.” The team writes the reasoning in an instructions file and lets lint enforce it. When a bug or drift shows up in review, testing, or an incident, they turn it into a rule, and “lint green” becomes the definition of done. That move is the bridge to Feedforward and Architecture Fitness Function, which cover how boundary rules are designed.
How to Recognize It
You’re working with static analysis whenever a tool reports on code it didn’t execute. The questions that tell you whether it’s working:
• Where do findings appear? A finding in the compiler or the agent’s edit loop gets acted on. A finding on a dashboard or in a weekly report mostly doesn’t. Google’s developers rated 74% of compile-time issues “real problems” against 21% of issues found in already-committed code, partly because by then testing and review had caught the important ones.
• What happens after a finding? Count fixes, suppressions, and silent ignores. That ratio is your effective false-positive rate, for people and agents alike.
• Can the reader act on the message alone? If an agent’s fix for a rule is usually wrong, read the diagnostic before you blame the model.
• Is the suppression count rising? Inline disables such as # type: ignore, eslint-disable, @ts-ignore, and # noqa are the analyzer’s debt ledger.
Four failure modes show up repeatedly once agents act on findings:
• Suppression creep. The agent silences a true positive to get a green check. This is Reward Hacking aimed at the analyzer. It’s common enough that one linter, thai-lint, ships a rule to catch suppression comments that AI assistants add without justification. A suppression with a stated reason on a genuine false positive is legitimate; CodeCureAgent suppresses the false positives it identifies. The silent, unexplained one is the problem.
• Fixing false positives into the code. The analyzer was wrong, and the agent “fixed” it anyway with a change that alters behavior: a default value where null was meaningful, or a caught exception that used to propagate.
• Noise that trains everyone to ignore output. A rule that fires on correct code teaches people to skim and teaches agents to suppress.
• Rule sprawl. Every review comment becomes a rule, no rule is ever retired, and the analyzer’s run time and false-positive rate climb together.
How It Plays Out
A payments team hands an agent a backlog of 612 linter and type-checker warnings and lets it run overnight on a branch. Each proposed fix has to pass the same gate: build, rerun the analyzers with no new findings, and run the tests. By morning, 571 fixes have passed. The tests rejected 19, most of them “possibly null” fixes that substituted an empty string where downstream code relied on None meaning “not yet billed.” The agent marked the remaining 22 as probable false positives and explained each one.
A reviewer samples 30 of the accepted fixes, reads all 22 dismissals, and finds two dismissals that were real bugs. Both get fixed by hand. The gate caught the fixes that changed behavior, and the human read caught the dismissals that were wrong. Neither check alone would have turned “the agent says it’s handled” into evidence.
A platform team notices that its type-check job has been green for a month while production keeps logging errors about values of the wrong type. A search shows that coding agents added 37 # type: ignore comments in two weeks, each one clearing a real type error in the change that introduced it. The team adds a CI step that counts new suppression comments in each diff and fails the build unless every new one carries a written reason and a reviewer’s approval. The instructions file gains one line: fix the type error or stop and explain it; never suppress it. New suppressions drop to one or two a week, and each comes with a reason someone can evaluate.
Tip: When you write a custom lint rule for agents, spend as much effort on the message as on the detection. Say what was found, why the project forbids it, and what to do instead, with the allowed import path or API named. The agent will act on exactly what the message tells it.
Consequences
Static analysis is the cheapest whole-codebase check most teams have. It runs in milliseconds to minutes, gives the same answer for the same input, considers paths no test reached, and needs no test data. That makes analyzers the natural first sensor in an agent’s loop. Agents also raise the value of analyzer output, because the cost of acting on a finding has dropped from minutes of human attention to cents of model time.
The costs move rather than vanish. A false positive used to waste a person’s attention; now it can produce a confident, behavior-changing patch, so precision matters more when agents read the output, not less. Suppression comments accumulate quietly unless something counts them. Rule sets grow faster than anyone prunes them.
A clean analyzer run proves only that the analyzer found nothing it looks for. Analyzers check code against rules and types, not against the requirement, so a function that cleanly computes tax at last year’s rate passes every one of them. That’s why the analyzer belongs beside the test suite and code review rather than in place of either.
Related Articles
Depends on: Feedback Sensor — Static analyzers are the largest family of computational feedback sensors, so the sensor vocabulary comes first.
Depends on: Test — Static analysis is easiest to understand by contrast with a test, which learns about behavior by running the code.
Detects: Smell (Code Smell) — Many linter rules are code smells written down precisely enough for a tool to find them.
Detects: Vulnerability — Static application security testing scans source code for known vulnerability patterns.
Enforces: Coding Convention — Formatters and linters turn a written coding convention into a mechanical check.
Informs: Feedforward — A recurring analyzer finding is a signal to write the rule into the agent's instructions so it avoids the mistake on the first pass.
Informs: Programming Language Selection — How much a language's tooling can check statically is one input to choosing it for agent-written code.
Specialized by: Formal Verification — Formal verification is the sound, expensive end of static checking: it proves a property instead of flagging likely violations.
Supports: Harnessability — A codebase with strong type checking and precise linters gives an agent more fast, deterministic feedback.
Used by: Architecture Fitness Function — Many architecture fitness functions are implemented as static checks over imports, dependencies, and module boundaries.
Used by: Autonomous Remediation — Autonomous Remediation turns analyzer findings into a detect, fix, and verify pipeline.
Used by: Code Review — Code review is where review-time analyzer findings surface and where reviewers decide which ones to act on.
Used by: Shift-Left Feedback — Shift-Left Feedback decides when in the loop analyzers run; this entry covers what they can and cannot know.
Used by: Verification Loop — A verification loop reruns analyzers after each change and feeds their findings back to the agent.
Violated by: Reward Hacking — An agent that silences a real finding with a suppression comment games the analyzer's signal instead of fixing the code.
Sources
• Caitlin Sadowski, Edward Aftandilian, Alex Eagle, Liam Miller-Cushon, and Ciera Jaspan, “Lessons from Building Static Analysis Tools at Google” (Communications of the ACM 61(4), 2018), is the source of the effective false positive, Google’s compiler and code-review criteria, the Tricorder feedback loop, the FindBugs history, and the diagnostic-text example.
• Stephen C. Johnson’s Bell Laboratories technical report Lint, a C Program Checker (1978) gave the family of style and bug-pattern checkers its common name.
• H. G. Rice, “Classes of Recursively Enumerable Sets and Their Decision Problems” (Transactions of the American Mathematical Society 74, 1953), proved the undecidability result that forces every analyzer to trade missed bugs against false alarms.
• Pascal Joos, Islem Bouzenia, and Michael Pradel, “CodeCureAgent: Automatic Classification and Repair of Static Analysis Warnings” (2025, revised 2026), describes an agent that triages and repairs analyzer warnings behind a build, reanalyze, and test gate.
• Xueying Du and colleagues, “Reducing False Positives in Static Bug Detection with LLMs: An Empirical Study in Industry” (2026), measures LLM-based false-alarm reduction on an enterprise analyzer at Tencent.
• Yunpeng Xiong and Ting Zhang, “Sifting the Noise: A Comparative Study of LLM Agents in Vulnerability False Positive Filtering” (ISSTA 2026), compares agent frameworks on security-scanner output and documents the risk of dismissing true vulnerabilities.
• Alvin Sng, “Using Linters to Direct Agents” (Factory, 2025), describes lint rules as the enforceable half of agent instructions and the practice of codifying review and incident findings as rules.
• The lazy-ignores rule in the thai-lint project documents suppression comments added by AI coding assistants as a recurring problem and requires a written justification for each.