Review the prompts in your code
Prompts hide in strings across a codebase, and their bugs only show in production: user input that rewrites the system prompt, a reply parsed as JSON that never asked for JSON, a model id retired months ago. Paste code or open files and see each prompt's problems, with the line and the fix. Run the same check in CI.
Read about the prompt bugs that break AI features in production, each with the fix.
New to this? What it does, in plain words
A linter reads code for mistakes before it runs. This one reads the prompts inside it.
It finds the strings in Python, JavaScript, TypeScript, YAML, JSON and prompt files that hold instructions for a model, works out which parts a user controls, and checks each prompt against the problems that break AI features in production.
What you need
Nothing. No API key and no sign-up. When you press Review, the code goes to thinkhard.si, is read and is forgotten. Files you open are read in this browser first.
How to use it
- Paste a file, or open several at once.
- Press Review the prompts.
- Each prompt shows its line, what it takes from users, and every problem with the fix.
- Add the command at the bottom to CI so every pull request is checked.
Code
Run it on every pull request
The same review from a terminal, with no install: it reads the files that look like they hold prompts, sends only those, and exits with an error when it finds one. Node 18 or later.
npx -y --package=https://thinkhard.si/downloads/thinkhard-mcp.tgz thinkhard-lint .
--sarif prompt-lint.sarif for code scanning, --markdown lint.md for a comment, --fail-on warning to be stricter.In GitHub Actions, with findings shown on the pull request's changed lines:
name: Prompt lint
on: [pull_request]
jobs:
prompts:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npx -y --package=https://thinkhard.si/downloads/thinkhard-mcp.tgz thinkhard-lint . --sarif prompt-lint.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: prompt-lint.sarif
.github/workflows/prompt-lint.yml.What breaks AI features in production
Most prompt bugs are not about wording. They are about where text comes from and what the code does with the reply. A unit test passes, then a user pastes "ignore your instructions", or the model starts its reply with "Sure, here's the JSON".
- Your code. Find the promptsStrings that read as instructions, in code, configs and prompt files.
- thinkhard.si engine. Trace the inputWhich parts a user controls: input, query, message, req.body.Instant
- thinkhard.si engine. Check each promptInjection, delimiters, JSON, caching, keys, the ten checks.
- thinkhard.si engine. Check the codebaseModel ids against the current catalog, copies of the same prompt.
- You or CI. Fix or fail the buildEach finding with its line and the fix; SARIF for code scanning.
The rules
- User input in the system prompt (error). Whatever a user types becomes part of your rules. Keep the system prompt fixed and send the user's text as the user message.
- User input with nothing around it (warning, or error in a system prompt). Without tags around it, instructions inside the user's text read like yours. Wrap it, for example in <user_input> tags.
- No rule that user text is data (warning). Tags help only when the prompt says what they mean.
- Reply parsed as JSON, but JSON never asked for (error), and JSON asked for, but not JSON only (warning). The code calls a JSON parser on the reply, and a reply in prose, or wrapped in a code fence, breaks it. Ask for JSON only, or use the provider's structured output.
- Changing part before the fixed part (note). Providers cache only an identical start of a prompt, so a long prompt that puts a variable near the top pays full price for everything after it.
- Key or token in the code or a prompt (error). Models can repeat their prompt and prompts end up in logs. Revoke it and load it from the environment.
- Model id not in the current catalog (warning). Checked against the catalog behind the price index, with the current models from the same maker.
- Personal data in a prompt, the same prompt in two places and a prompt that misses most of the ten checks (notes).
Questions
Which languages does it read?
Python, JavaScript and TypeScript (including JSX and TSX), YAML, JSON, Markdown, plain text, and prompt files such as .prompt, .prompty, Jinja and Handlebars. In code it finds long strings, template literals and f-strings that read as instructions to a model, and strings given to a name such as SYSTEM_PROMPT or a key such as system, instructions or content.
How does it know which input comes from a user?
From the names: variables such as input, query, message, question, text, document, req.body or request.json are treated as text a user controls. A prompt that inserts them is checked for what that text could do.
Is my code stored?
No. The code is sent to thinkhard.si when you press Review, read, and forgotten; nothing is written to disk or logs. The command line version sends only files that look like they hold prompts. See the privacy page.
Can it fail a build?
Yes. thinkhard-lint exits with code 1 when it finds an error, or a warning with --fail-on warning, and writes SARIF so GitHub shows each finding on the line it is about.
Can an AI coding assistant use it?
Yes. Assistants that use the thinkhard.si MCP server get it as the lint_prompts tool, and POST /api/v1/lint returns the same result as JSON.