Skip to content
Salyro
Tool

Prompt linter

Check a prompt with fixed rules, no model involved — unfilled {{variables}}, contradictions, JSON without a schema, cache-breaking dates, hidden text, keys.

What it does

Paste a prompt — a system prompt, a template, or a whole prompt as you send it — and the linter checks it against a fixed catalogue of rules:

  • template variables that were never filled in: {{name}}, ${NAME}, {name};
  • instructions that contradict each other, by simple word patterns;
  • JSON asked for with no schema or example of its shape;
  • content that changes on every request — a date, an id, a placeholder — early in the prompt, where it keeps the rest from being read from a cache;
  • characters nobody can see: tag characters that spell hidden text, bidirectional controls, zero-width characters;
  • credentials: API keys, tokens and passwords, always shown masked;
  • a prompt long for the chosen model's context window.

Each finding points at its place in the text and at the rule below. Everything runs in your browser: nothing you paste is sent anywhere, and no model reads your prompt.

How it checks

Deterministic rules only. Every rule is a pattern, a count or a documented fact. There is no quality score and no model's judgement: the same prompt always gives the same findings, and each finding says which rule made it.

No finding is an error. A prompt is text you meant to write, and nothing here can prove a provider will refuse it or a model will misread it. A finding is a warning when the pattern is exact — a credential's shape, an invisible character, a placeholder left in — or when the chosen model's provider documents the behaviour the rule relies on. Every finding that rests on a heuristic of this tool's own is a note, and says so.

Credentials are never shown. Every fragment of your prompt a finding quotes — a placeholder, an instruction, hidden text — has any credential in it replaced by its masked form (the public prefix at most, and the length) before it is cut to length. That holds for "Copy findings" too.

Where a rule comes from. Three rules depend on the provider: JSON without a schema, content that changes early in the prompt, and length. Their sources are recorded as Reference Data, per provider. For Anthropic, each quotes Anthropic's own page. For OpenAI, Google and xAI, the pages could not be read when the rules were compiled, so they show unknown and the rule stays a heuristic. Choose a model to see what its provider documents; with no model, the linter shows all four.

Shared parts, not copies. Credentials are found by the same detector as the PII & Secret Redactor and the API-key Leak Scanner, over Salyro's own log-redaction patterns. Invisible characters are read by the same Unicode module as the Invisible Text & Homoglyph Detector. {{placeholders}} are read by the template engine the batch tools render with. The token count comes from the token counter's layer: exact for OpenAI models, a marked estimate for the others.

The rules

Rule: unreplaced variable

Template syntax left in the prompt: the model would read it as it is.

  • {{name}}, read exactly as the site's template engine reads it (where \{{ is a literal {{): a warning.
  • ${name}, a JavaScript template literal's or a shell's variable: a warning.
  • Notes, because each is as often meant literally:
    • double-brace syntax that is not a plain name: a block helper such as {{#each items}}, a comment, or an object such as JSX's style={{color: "red"}};
    • {{name}} right after =, as in a JSX or template attribute;
    • ${HOME} or ${PATH}, a name in capitals as environment variables are written, which a shell sample holds on purpose;
    • {name} or {user.city}, Python's str.format or an f-string. JSON is never read as a placeholder: a name in quotes is not an identifier.
    • a {{ or ${ that never closes.

Code is skipped. Template syntax inside Markdown code — a fenced block (``` or ~~~) or inline code in backticks — is not reported: a ${name} in a JavaScript sample, or {{name}} in text that explains Handlebars, is the example. Inline code is read line by line. To have literal braces left alone, put them in code.

Each distinct placeholder is one finding, at its first use, with how often it appears. The Prompt Template Renderer fills {{name}} placeholders with the same engine.

Rule: contradictory instructions

Two instructions that ask for opposite things, found by simple word patterns — never by understanding the prompt.

  • "always …" or "must …" against "never …", "do not …", "don't …" or "must not …", when the next two or three words are the same: "Always use bullet points" and "Never use bullet points". A note: it is a word match.
  • An ask for a short answer ("be concise", "in one sentence") and one for a long one ("in detail", "a detailed explanation"); or "JSON only" and "use markdown". A note.

It is a word match. If a condition already separates the two — "never answer in English if the user writes in French" — the finding is wrong, and says it may be.

Rule: JSON without schema

The prompt asks for JSON — "respond in JSON", "return … as JSON", "JSON only" — and gives no schema or example of its shape: no JSON Schema, no example object, no typed field list and no json code block.

For an Anthropic model it is documented, and a warning. Anthropic's structured outputs page states: "Without structured outputs, Claude can generate malformed JSON responses or invalid tool inputs that break your applications." With no model, or another provider's, it is a heuristic, and a note. The fix is to give the shape, or to use the provider's structured output with a schema, which is enforced rather than asked for. JSON to JSON Schema writes one from an example.

Rule: dynamic content at start

Something that changes from one request to the next, early in the prompt, keeps the static text after it — instructions, examples, reference data — from being read from a cache: a cache is read only up to the first difference.

What is documented is only that last part, the provider's prefix matching. Whether a given piece of your prompt changes is this tool's reading, so the rule reports two things differently:

  • A template placeholder ({{name}}, ${name}) is filled in per request by definition. For an Anthropic model it is a warning, documented by Anthropic's prompt caching page: "Cache hits require 100% identical prompt segments, including all text and images up to and including the block marked with cache control", and "Place the breakpoint on the last block that stays identical across requests. For a prompt with a static prefix and a varying suffix (timestamps, per-request context, the incoming message), that is the end of the prefix, not the varying block." For OpenAI, Google and xAI prefix matching is unknown here, so it is a note.
  • A date, a time, "today is", an id (a UUID) or a number shaped like a Unix timestamp only may change: "Our policy changed on 2024-03-01" is fixed, and a phone number is not a timestamp. It is always a note, worded as one, and reported only when it comes before the first placeholder.

Either is reported only when enough text follows it to be worth caching: more than the chosen model's documented minimum cacheable prompt, from the prompt caching Reference Data. With no model, or a model whose minimum is unknown, the threshold is the smallest minimum any provider page in Reference Data documents. The count after the value is an estimate (characters ÷ 4). Put what stays the same first and what changes at the end. The prompt caching calculator prices the difference.

Rule: invisible characters

Characters no font draws, in the ranges the Invisible Text & Homoglyph Detector defines. Tag characters (U+E0000–U+E007F) spell ASCII a model reads and a person does not — the finding decodes them, with any credential in the decoded text masked, and names tag characters by their kind rather than letter by letter. Bidirectional controls change the order text is displayed in. Both are warnings. Zero-width characters, unusual spaces and control characters are notes. Ordinary uses are not reported: a zero-width joiner in an emoji, an emoji's presentation selector, the tags of a subdivision flag, a joiner in Persian spelling. Adjacent characters are one finding. To see and clean each one, use the Invisible Text & Homoglyph Detector.

Rule: secret in prompt

A credential in the prompt: an API key or token by its documented shape, a private key, a password in an address, or a value under a credential's name (DB_PASSWORD=…, "api_key": "…"). A warning, because a prompt is sent to the provider and is often logged. A value that reads like a placeholder (your-key-here) is a note. The value is never shown: a finding shows the key's public prefix at most, and its length. A key hidden in the common ways is found too, and masked whole: written in invisible tag characters, split between visible text and tag characters, or broken or glued to a word by zero-width, bidirectional or control characters. A redactor's placeholder such as [ANTHROPIC_API_KEY_1] is not reported. Remove the credential, and revoke it at its issuer if it is real.

The detector is the one the PII & Secret Redactor and the API-key Leak Scanner use, and its limits are theirs: it reads a credential by its shape or by the name it sits under. A key split across lines is masked whole only when its issuer defines its length (a Salyro API key, an MCP Expose token); any other is found up to the line break, and the rest of it is not. A base64-encoded key with no telling name next to it has no shape to recognise, and is not found.

Rule: unusual length

The prompt set against the chosen model's context window, from Reference Data. Longer than the window: a warning where it is documented — for an Anthropic model, by Anthropic's context windows page: "If the input alone already exceeds the model's context window, the API returns a 400 invalid_request_error". More than half the window: a note, and half is this tool's own threshold, not a provider limit — the rest has to hold the conversation and the answer. Where the window is unknown, length is not checked, and the page says so. The count is exact for OpenAI models and an estimate for the others; the window is checked against whichever it is.

Sharing

A share link holds your prompt, after the # of the address. Your browser never sends that part to a server, but anyone you give the link to can read it — so the link never holds a credential: it keeps the prompt with each one replaced by a placeholder — a key in tag characters by a placeholder in tag characters, so the hidden text still shows — and holds no prompt at all if one would still be seen. Share settings only leaves the prompt out.

What it does not know

  • It does not understand the prompt. A contradiction is a word match, and a prompt with no finding is not a good prompt — only one these rules found nothing in.
  • It reads one text. It does not see the rest of the request — tools, earlier turns, images — or how your code fills in the placeholders.
  • The OpenAI, Google and xAI rules are heuristics until their documentation can be read, and most of their context windows are unknown.
  • Token counts for Anthropic, Google and xAI models are estimates, so length and the caching threshold are approximate for them.
  • It finds credentials by shape and by name. A secret with neither is not found.
  • Credential detection is best-effort. It catches keys pasted as they are, including keys hidden or split by invisible characters in the common ways. An input built on purpose to evade it can still get past it, so never paste a secret into a prompt you share.

FAQ

Does it use a model to check my prompt?

No. Every rule is a fixed pattern, a count or a documented fact, and the same prompt always gives the same findings. Nothing is sent anywhere.

Why is nothing an error?

None of these rules can prove a request will be refused. Where a provider documents the behaviour, the finding says so and links to the page.

Why does a date at the start of my prompt matter?

A prompt cache is read only up to the first difference. If the first line changes on every request, nothing after it is read from the cache. Put the date at the end.

Sources

Every provider-dependent rule's source — the page, the date it was read and the sentence quoted, or unknown with the page that was tried — is Reference Data, listed in the public tools' data freshness report: prompt-rules.json for the JSON and length rules, and for the caching rule cache-prefix.json — the same facts the Prompt Diff reads about how each provider's cache matches a prompt. The context windows come from model-limits.json and the minimum cacheable prompts from prompt-caching.json. The Anthropic rules quote three pages on platform.claude.com: prompt caching, structured outputs and context windows.

Where the numbers come from

  • Model and pricing data is a snapshot of the catalogue Salyro itself uses, taken when this site was built.
  • Facts about models and providers that are not in that catalogue carry their own source and the date they were checked, and are shown as unknown where no reliable source exists.