Error Messages skill for Codex
What happened, why, and what to do next. Codex inventories every error site with ripgrep, ranks the ones that generate support tickets or dead ends, and traces each back to its real causes so one vague message becomes several specific ones. Rewrites name the offending value and a next step the reader controls, API errors move to RFC 9457 problem details with stable codes, and strings stay translatable.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- error-messages Installs to ~/.agents/skills/error-messages/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install error-messages The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project error-messages Installs to .agents/skills/error-messages/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/error-messages.zipunzip -o error-messages.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) error-messages PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $error-messages, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Writes and audits error messages people can act on - in UIs, CLIs, API responses (RFC 9457 problem details), SDK exceptions and logs - stating what happened, why, and what to do next, with stable error codes, the offending value, no blame and no leaked secrets or internals. Inventories existing messages with ripgrep, rewrites the worst offenders, and keeps strings translatable. Use when error text says "Something went wrong" or "Invalid input", when designing an API error format or error code catalog, when support tickets trace back to confusing errors, or when reviewing user-facing failure states.
Use it when
- Users or support tickets point at confusing or generic error text
- An API needs a consistent error format and stable error codes
- Reviewing failure paths in a UI, SDK or CLI before release
Use something else when
- You are designing how errors propagate and are typed in code: use error-handling-patterns
- You are designing full loading, empty and error UI states: use ui-copy-states
- The problem is --help output or usage errors of a CLI: use cli-help-text
How to check the result
Each rewritten error has a test that triggers it and asserts its code and key phrase, the generic-message grep returns only intentional cases, and no client-facing error exposes secrets or internals.
The mistake it prevents
Rewording an error string that client apps, tests or alerts match on by exact text, breaking them silently instead of matching on a stable code.
An error message is done when the person who sees it knows what failed, whether it was their input or the system, and the next step to fix or work around it, without opening the source code or contacting support. Every message has an audience: an end user, a developer integrating with you, or an operator reading logs. Write for that one.
Anatomy
| Part | Answers | Example |
|---|---|---|
| What happened | Which operation failed, on what | “Could not upload report.pdf.” |
| Why | The cause in the reader’s terms, with the offending value | “The file is 32 MB; the limit is 25 MB.” |
| What to do | A concrete next step | “Compress the file or split it into parts.” |
| Reference (optional) | Error code, request ID, docs link | “Error FILE_TOO_LARGE. Request ID 7f3c…” |
Not every message needs all four. Validation errors next to a form field often need only “why + what to do”: “Enter a date after today.”
Audience rules
| Audience | Where | Include | Exclude |
|---|---|---|---|
| End user | UI, email, mobile | Plain words, fix in their control, support path if not | Stack traces, codes as the main text, internal names, blame |
| Developer | API responses, SDK exceptions, CLI | Stable code, field path, received vs expected value, docs link | Secrets, other users’ data, internal hostnames, SQL |
| Operator | Logs, alerts | Operation, identifiers, upstream response, retry count, correlation ID | Raw tokens, passwords, full PII |
Inspect first
Inventory what exists before rewriting anything:
cat AGENTS.md 2>/dev/null | head -60
rg -n --no-heading "(throw new \w*Error|raise \w+(Error|Exception)|errors\.New|fmt\.Errorf|anyhow!|bail!)\(" -g '!**/test*' | head -100
rg -n -i "something went wrong|unknown error|invalid input|an error occurred|oops|failed\.?['\"]" -g '!node_modules' | head -50
ls locales i18n src/locales 2>/dev/null # are strings already externalised?
rg -n "problem\+json|errorCode|error_code" | head # existing API error format
Pull real cases from support tickets, logs or analytics if the user can share them; the messages that generate tickets are the ones to fix first.
Workflow
- [ ] 1. Inventory messages (commands above); note audience and frequency per message
- [ ] 2. Rank: high-traffic and ticket-generating first, then dead-end messages (no next step)
- [ ] 3. For each: identify the real cause(s) in code; split one vague message into specific ones
- [ ] 4. Rewrite with the anatomy and rules below
- [ ] 5. Add stable codes and docs links where developers consume the error
- [ ] 6. Keep strings translatable (placeholders, no concatenation)
- [ ] 7. Add tests that trigger each error and assert code and key text
- [ ] 8. Report the catalog of changed messages
Writing rules
- Be specific. One generic message for five causes is five support tickets. Split by cause: “Email already registered” vs “Email domain not allowed”.
- Show the value and the constraint. “Port 80800 is out of range (1-65535).”
- Say what to do, in the reader’s control. “Run
acme loginto refresh your session.” If nothing is in their control: “Try again in a few minutes. If it keeps happening, contact support with code PAY-502.” - No blame, no drama. “That password is incorrect” over “You entered an invalid password!” No “Oops”, “Uh oh”, exclamation marks or jokes in errors that cost money or data.
- Plain verbs. “Could not save” over “Save operation failed due to an exception”.
- Preserve user work. Never clear a form on error; say what was kept.
- Placement. Field errors next to the field and announced to assistive tech
(
aria-describedby,aria-invalid); page-level errors at the top with links to fields. - Security. Login errors must not reveal whether an account exists (“Email or password is incorrect”); never echo secrets; do not expose stack traces, SQL or internal service names to clients.
Before and after
| Before | After |
|---|---|
| Invalid input | Enter a phone number with country code, for example +44 20 7946 0958. |
| Error 500 | We could not save your changes because of a problem on our side. Your draft is kept. Try again in a minute. |
| ENOENT: no such file or directory | Config file not found at ~/.acme/config.toml. Run acme init to create one, or pass --config <path>. |
| Permission denied | You need the Editor role to publish. Ask a workspace admin to change your role. |
| Request failed | Payment declined by your bank (insufficient funds). Use another card or contact your bank. |
| ValueError: bad value | timeout must be a positive number of seconds; got -5. |
API errors (RFC 9457 problem details)
Use one error format across the API. RFC 9457 (which obsoletes RFC 7807) is the standard default:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://docs.acme.dev/errors/validation",
"title": "Request validation failed",
"status": 422,
"detail": "2 fields are invalid.",
"instance": "/v1/invoices/req_7f3c9a",
"code": "VALIDATION_FAILED",
"errors": [
{ "pointer": "/amount", "detail": "Must be a positive integer in cents; got -500." },
{ "pointer": "/currency", "detail": "Unsupported currency 'usdd'. Use an ISO 4217 code such as 'usd'." }
]
}
typeis a stable URI that documents the problem;titleis constant per type;detailis specific to this occurrence. Extra members (code,errors) are allowed.- Stable machine-readable codes (
VALIDATION_FAILED,CARD_DECLINED) are an API contract: clients branch on them. Never change or reuse a code; add new ones. - Status codes carry meaning: 400/422 client input, 401 unauthenticated, 403 forbidden,
404 not found, 409 conflict, 429 rate limited (with
Retry-After), 5xx server-side. - Return a request or correlation ID on every error and log it server-side.
- Document every code in an error catalog (see
$api-documentation).
SDK exceptions and CLI errors
- Exception messages: one sentence, include the offending value and the constraint, no trailing period needed in log-style messages; follow the language’s convention (Go error strings are lowercase with no trailing punctuation, per Go style).
- Wrap with context as errors cross layers:
loading config: parsing ~/.acme/config.toml: line 12: expected '=' after key. Each layer adds what it was doing. - CLI: errors to stderr, non-zero exit code,
error:prefix, one suggested command. For usage errors and help output, see$cli-help-text.
Translatable strings
- Use full-sentence templates with named placeholders:
"File {name} is {size} MB; the limit is {limit} MB.". Never build sentences by concatenation; word order changes between languages. - Use ICU MessageFormat or the platform’s plural support for counts
(
{count, plural, one {# field is invalid} other {# fields are invalid}}). - Give translators context comments: where the message appears and what placeholders are.
- Keep error codes untranslated; translate only human text.
Gotchas
- Logging the full request body “for debugging” puts passwords, tokens and card data in logs. Log identifiers and redacted fields.
- Rewriting a message that tests, client code or monitoring alerts match on by text.
Search for the old string (
rg -F "old message") and update matchers, or match on codes. - Messages that tell the user to “contact your administrator” when the user is the admin.
- Translating error codes, or changing them in a “copy cleanup”.
- Retry advice on non-retryable errors (a 400 will fail again) or missing retry advice on retryable ones (429, 503).
Verification
- Every changed error has a test that triggers it and asserts the code and key phrase.
- Grep for remaining generic messages: the inventory query above returns only reviewed, intentional cases.
- For each rewritten message, the next step is something the stated audience can do.
- No message includes secrets, stack traces or internal hostnames in client-facing output.
- API errors validate against the documented problem schema.
Report
**Inventory:** 132 error sites; 41 user-facing, 63 API, 28 log-only
**Rewritten:** 27 (top ticket generators first); 6 generic messages split into 15 specific ones
**API:** problem+json adopted for /v1/invoices and /v1/payments; 12 codes added to docs/errors.md
**Tests:** 27 error-path tests added/updated
**Compatibility:** 3 old messages matched by the mobile app by text; kept old text in `detail` until app v5.2 ships (flagged) What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Error Messages"
short_description: "What happened, why, and what to do next"
default_prompt: "$error-messages Find every 'Something went wrong' in the app and replace them with specific messages" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.