Reading Hound's verdict

How to read the review Hound posts on a pull request — severity tiers, the categories it covers, what a suggested fix looks like, and the playbook for acting on the verdict.

1

Severity tiers

Hound asks the underlying model to label every finding with one of four real severity tags, parsed from the response in services/review.js. These are the only tiers you'll see in a verdict:

  • CRITICAL — must block merge. Logic bugs that corrupt data, remote code execution, leaked secrets, auth bypass, or anything that ships broken behavior to real users. Treat as "do not merge until fixed."
  • HIGH — should block merge. Significant correctness issues, unsafe patterns, missing input validation in a public surface, race conditions, or anything that will hurt users in production even if it doesn't fully break them.
  • MEDIUM — will not block merge on its own, but the author should respond. Edge cases that bite under load, error-handling gaps that swallow failures, non-trivial maintainability problems, or noticeable performance issues.
  • LOW — informational. Naming, comment clarity, minor stylistic notes. Do not nitpick (the prompt in review.js explicitly tells the model not to).

What Hound has already done about it. Nothing — Hound only posts a comment. It never auto-merges, auto-requests-changes, or otherwise touches PR branch state. You (a human maintainer) decide what to do with the verdict.

Example finding for each tier, lifted from the prompt's brief in services/review.js:

CRITICAL — SQL concatenation with user input on POST /search (db/queries.js:42) — classic injection. Use parameterized queries: db.query('SELECT ... WHERE id = $1', [id]).
HIGH    — Token stored in plaintext on User model (db/users.js:19) — replace with bcrypt.hash(token, 12) and store the hash.
MEDIUM  — Bulk import swallows DB errors (routes/import.js:88) — log the row id and rethrow; today a single bad row silently drops the whole batch.
LOW     — Inconsistent naming on the response helper (lib/respond.js:3) — pick one of {success, ok, status} across the codebase.
2

Code-category taxonomy

Hound doesn't store a findings.category column — categories live only as descriptive labels in the comment. They mirror the focus areas the review prompt in services/review.js asks the model to cover, so what Hound tells you about lines up with what its brief says to look for:

  • Logic / correctness. Off-by-one bugs, wrong branching, misinterpreted types/contracts, race conditions, behavior that contradicts the test.
  • Security. Injection, secrets hardcoded or logged, insecure defaults, missing authn/authz checks on a public route, CSRF/SSRF, unsafe deserialization.
  • Performance. N+1 queries, unbounded loops, missing indexes the query obviously needs, large allocations on a hot path, synchronous I/O in async handlers.
  • Error handling. Swallowed exceptions, missing catches on awaited promises, error messages that lose context, retries without backoff, partial-failure paths that leave state half-updated.
  • Maintainability / style. Naming, dead code, surprising control flow, magic numbers, duplicated logic that could be a helper. The prompt explicitly tells the model to skip pure stylistic nitpicks — only meaningful readability issues land here.

One-line example per category, again shaped like the model's natural output:

Logic     — route returns 200 even when no row matched (routes/items.js:54) — should be 404.
Security  — /admin accepts the role from the request body (routes/admin.js:12) — derive server-side from the session.
Perf      — listUsers does a query inside a for-loop (routes/users.js:30) — collect ids first, then IN(...).
Errors    — try/catch around fetch logs and continues (services/sync.js:18) — rethrow or surface to the caller.
Style     — three near-identical mapping helpers (lib/map-*.js) — collapse into one with a type arg.

A single finding can hit more than one category (e.g., a missing input check is both security and logic). When that happens Hound names the dominant one — the comment, not a schema, is the source of truth.

3

Suggested-fix format

What you see in the PR comment is the human-readable review_text — a summary paragraph followed by severity-tagged findings. Each finding has the same four-line shape that the review prompt in services/review.js asks the model for:

HIGH — `db/queries.js:42` — User-controlled `q` is concatenated into the raw SQL.

  Strings built by concatenation bypass parameterized queries and let an
  attacker end the query and append their own. Any string field that
  reaches `db.query` raw is a candidate.

  Fix: pass `q` as a parameter and let the driver bind it —
    db.query('SELECT id, name FROM users WHERE name ILIKE $1', [`%${q}%`])
  Also escape `%` / `_` from the user input if you wire up LIKE wildcards.

Anatomy of each finding:

  • Header line. <SEVERITY> — file:lineone-line summary. The severity tag here is what's parsed into findings[].severity by the regex on services/review.js:60.
  • Description. 1–3 short paragraphs explaining what's wrong and why. Includes the file + line context.
  • Inline fix. A concrete code suggestion you can paste. Not always a full diff — the model gives the smallest change that fixes the bug.

The full verdict (the prose summary plus every tagged finding) is also stored on the reviews table as review_text (TEXT) and findings (JSONB) — see the API reference for the exact response shape. The PR comment is the rendered form of the same record.

4

Decision rules

This is the human-side playbook, not a contract. Hound does not auto-merge, auto-request-changes, or auto-comment today — the comment on the PR is its only output. Treat the rules below as guidance for the maintainer reviewing the verdict.

  • No CRITICAL or HIGH findings → safe to merge. The verdict is clean enough to ship. MEDIUM / LOW findings are still worth a glance, but on their own they shouldn't block.
  • Any CRITICAL or HIGH finding → request changes. The author should push a fix commit and re-trigger review (push to the same PR works — the webhook fires on every commit). Re-review the updated diff before merging.
  • MEDIUM only → comment, then merge. MEDIUMs signal "this will bite us eventually." Ask the author for an in-thread reply (acknowledgement, plan, or a fix), but it doesn't have to land before merge.
  • LOW only → comment, merge as-is. Maintainer's call whether to fix forward or batch into a cleanup PR. Don't gate the merge on these.

Edge cases:

  • False positive. Reply on the PR with the reasoning and dismiss in-thread. The verdict doesn't gate merges — your judgment does.
  • "Looks good" verdicts. If the diff is clean, the model says so explicitly with a short summary. An empty findings list means no CRITICAL/HIGH were surfaced — it does not mean the PR has been audited end-to-end. Pair the verdict with your own review for high-risk changes.
  • Severity disagreement. The label is on the comment; you can re-label in your reply ("I'd call this HIGH, not MEDIUM") and act on your read. The stored findings[].severity is the model's call, not policy.

← Back to the quickstart · API reference · Setup guide · Troubleshooting