Codex Skills: Production-Grade Engineering Guide (2026)

Uncategorized

Version: 2026-11-01
Target: Codex CLI and IDE extension; local repository skills; optional custom prompts; lifecycle hooks; GitHub Actions
Operating examples: macOS/Linux/WSL with Python 3 and Git
Scope: creating, updating, deploying, discovering, invoking, testing, and enforcing two reproducible skills

Read this correction first. Codex repository skills belong in .agents/skills/<name>/SKILL.md in the current documented layout, not .codex/skills/. Native explicit invocation uses $refactor, not /refactor. Legacy prompt files can expose /prompts:refactor, but custom prompts are deprecated. Intent-based skill selection is probabilistic; reliable event-driven checks require lifecycle hooks and/or CI. Do not invent a hooks: or on_file_change: key in SKILL.md frontmatter.

Contents

  1. Architecture and execution sequence
  2. File and directory blueprint
  3. Exact skill schema and metadata rules
  4. Explicit $refactor implementation
  5. Implicit db-performance-review implementation
  6. Lifecycle event automation (guaranteed supported-tool check)
  7. CI enforcement and tests
  8. AGENTS.md orchestration
  9. Installation and deployment
  10. Optional legacy /prompts:refactor command
  11. Validation matrix and live smoke tests
  12. Updating, rollout, rollback, and security
  13. Troubleshooting and operational boundaries
  14. Official references

1. Architecture and execution sequence

Codex has four different extension surfaces. Do not confuse them.

MechanismFile(s)How it activatesGuarantee
Skill discovery and instructions.agents/skills/<name>/SKILL.mdExplicit $name or model selection from descriptionSkill content is provided when selected; implicit selection is not mandatory
Repository guidanceAGENTS.mdCodex builds instruction chain for current directory at session startGuidance is provided to model; it is not a shell-event handler
Tool lifecycle hook.codex/hooks.json + scriptSessionStart, PostToolUse, etc., for supported eventsHook runs when enabled, trusted, and matched; some tool paths opt out
CI policy.github/workflows/*.ymlPull request, push, manual dispatchCI runs independently of an agent session, subject to pipeline availability

1.1 Precise high-level execution model

flowchart TD
  A[Codex starts in a Git repository] --> B[Load global and applicable AGENTS.md]
  A --> C[Discover skill name, description, path]
  D[User task arrives] --> E{Explicit $skill?}
  E -- Yes --> F[Resolve named SKILL.md]
  E -- No --> G[Model considers intent vs descriptions]
  G -- Selected --> F
  G -- Not selected --> I[Normal Codex task handling]
  F --> H[Load skill instructions and optional resources]
  H --> I
  I --> J[Propose local tool call]
  J --> K[Matching PreToolUse hooks if configured]
  K --> L[Approved tool runs]
  L --> M[Matching PostToolUse hooks]
  M --> N[DB file hash comparison + static analysis]
  N --> O[Additional review context to Codex]
  O --> P[Verification and final response]

Codex starts with skill metadata (name, description and path). It does not need to read every full SKILL.md upfront. If a skill is explicitly named or selected by the agent, Codex loads its body and any needed references/scripts. A skill is instructions + resources, not an independently executing file watcher, tool permission system, or automatically registered /name route.

1.2 Trigger semantics

Input/actionExpected behavior
$refactor src/lib/user.tsExplicitly select the refactor skill, if installed and discoverable
/skillsOpen Codex’s skill browsing/selection interface, not execute /refactor
Refactor this module (without $refactor)May select a relevant implicit skill; depends on metadata and runtime policy
Optimize this SQL queryModel may select db-performance-review from its description
Codex edits db/queries/list.sql with a supported toolMatching PostToolUse hook can detect the changed file and run static analysis
Developer edits a file externally while Codex is idleA Codex tool hook does not fire immediately; use editor file-watch tools or CI for that case
Pull request modifies prisma/schema.prismaIndependent GitHub Actions workflow can run static tests

False promise to avoid: YAML keys such as triggers:, filePatterns:, onSave:, hooks:, events:, or watch: in skill frontmatter do not define Codex lifecycle events. They will not provide guaranteed automatic invocation. Put hook configuration in .codex/hooks.json or a supported config.toml hooks table.

1.3 Scope and priority of AGENTS instructions

Global guidance: $CODEX_HOME/AGENTS.md (typically ~/.codex/AGENTS.md), unless AGENTS.override.md is present. Project instructions are discovered from repository root down to the current working directory; per-directory AGENTS.override.md takes precedence over AGENTS.md. Nested instructions apply to relevant directories. AGENTS.md should provide concise engineering policy, not try to simulate a file watcher.

2. Directory blueprint

my-project/
├── AGENTS.md                        # repository-wide model guidance
├── .agents/
│   └── skills/                       # actual Codex skill discovery root
│       ├── refactor/
│       │   ├── SKILL.md              # required
│       │   └── agents/openai.yaml    # optional UI + explicit-only policy
│       └── db-performance-review/
│           ├── SKILL.md              # required
│           └── agents/openai.yaml    # optional UI + implicit policy
├── .codex/
│   ├── hooks.json                    # tool/session lifecycle handlers
│   └── hooks/db_performance.py       # deterministic hook implementation
├── .github/workflows/
│   └── db-performance.yml           # independent PR guardrail
├── tests/test_db_performance.py     # standard-library hook tests
└── src/                              # your application, not included here

Supported skill scopes: $CWD/.agents/skills, ancestor .agents/skills paths up to Git root, $HOME/.agents/skills, /etc/codex/skills, plus bundled system skills. A skill can include optional scripts/, references/, and assets/ if the workflow needs them. The examples deliberately omit empty placeholders.

What .codex/ is for: repository config and hooks, not the current documented repository skill root. ~/.codex/ is also the default Codex home for global AGENTS.md, config.toml, and the deprecated custom prompt folder.

3. Exact SKILL.md schema

A minimal valid skill:

---
name: example-skill
description: Analyze source files for a specific, well-defined problem. Use when the user asks for this analysis or when directly editing matching modules.
---

# Example Skill

1. Locate the scoped files and project instructions.
2. Carry out the workflow.
3. Run verification.
4. Report actions, results, and limitations.

Key rules:

  • SKILL.md is required; name and description are required frontmatter fields.
  • Keep names short, lowercase, action-oriented, with hyphens as needed. Match the folder name.
  • Write the description as a routing contract: the task, exact positive trigger contexts, and reasonable exclusions.
  • Keep the body imperative and execution-focused; put large domain references under references/ if warranted.
  • agents/openai.yaml can configure display information, a default prompt and policy.allow_implicit_invocation.
  • policy.allow_implicit_invocation: false is documented for explicit-only skills; test it in your installed CLI because previous releases had discovery regressions.
  • Avoid fabricating argument-hint, allowed-tools, model, hooks, or paths as a Codex skill event API. These may belong to other environments or deprecated custom prompts and are not a guaranteed Codex skill contract.

3.1 Optional metadata: explicit only

interface:
  display_name: "Refactor"
  short_description: "Safely refactor existing code without behavior changes"
  default_prompt: "Use $refactor to refactor a specified scope."
policy:
  allow_implicit_invocation: false

3.2 Optional metadata: implicit allowed

interface:
  display_name: "DB Performance Review"
  short_description: "Check database changes for query and schema risks"
  default_prompt: "Use $db-performance-review for the database changes."
policy:
  allow_implicit_invocation: true

4. Explicit skill: $refactor (complete source)

Use this skill for explicitly requested behavior-preserving changes. It protects interfaces, tests, and unrelated work. It is not an alias for arbitrary feature rewrites.

.agents/skills/refactor/SKILL.md — required instructions

---
name: refactor
description: Safely refactor existing application code while preserving externally observable behavior, public APIs, and data contracts. Use only when the user explicitly requests the $refactor workflow to improve readability, modularity, maintainability, or complexity without changing features.
---

# Behavior-Preserving Refactor

## Input contract

Accept a file, module, function, folder, or explicitly identified code region and optional goals. If scope is ambiguous, inspect the current changes and ask for a bounded target before touching unrelated code. Treat the user's requirements as the source of truth.

## Procedure

1. Inspect `AGENTS.md`, nested instructions, the relevant files, nearby tests, and `git status --short`. Identify unrelated work and leave it untouched.
2. State a concise refactor plan: target, invariants, proposed changes, and verification commands.
3. Identify behavior contracts: return values, errors, side effects, API shape, serialization, persistence, concurrency, performance-critical paths, and security checks. Preserve them.
4. Establish a baseline by running the narrowest available tests. If tests do not exist, add focused characterization tests before making consequential edits, where feasible.
5. Apply the smallest coherent change. Prefer named helpers, clearer control flow, reduced duplication, and bounded functions over broad rewrites. Do not silently add dependencies, change database schema, alter public APIs, or alter runtime behavior.
6. Re-run targeted tests, type checks, and linters. When feasible, compare results with the baseline; run the broader relevant suite only when justified.
7. Review the resulting diff (`git diff --check`, `git diff`) for regressions, extraneous edits, generated output, secrets, and accidental formatting churn.

## Safety rules

- Do not delete, reset, or overwrite uncommitted user work.
- Do not run destructive commands or modify production infrastructure without explicit authorization.
- Do not imply successful verification if a command was skipped, unavailable, or failed.
- Keep behavior-changing opportunities in a separate recommendation rather than smuggling them into the refactor.
- Never commit or push unless the user requests it.

## Output contract

Return four concise sections:

- **Scope:** files/functions changed and why.
- **Behavior preserved:** key invariants and any limitations.
- **Verification:** exact commands and actual outcomes; explicitly note tests not run.
- **Risks / follow-ups:** any remaining issues or suggested separate improvements.

.agents/skills/refactor/agents/openai.yaml — explicit-only invocation

interface:
  display_name: "Refactor"
  short_description: "Safely refactor existing code without behavior changes"
  default_prompt: "Use $refactor to refactor the specified files while preserving behavior and verifying tests."
policy:
  allow_implicit_invocation: false

Invocation

Launch Codex from repository root:

codex

Then type in the Codex composer:

$refactor Refactor src/services/userService.ts to reduce complexity.
Preserve public APIs, return values, error types, and database behavior.
Run focused tests, type checks, and show the changed files.

The skill doesn’t require an AGENTS.md reference to load when invoked explicitly, but the repository’s AGENTS.md provides independent operating rules.

5. Dynamic recognition skill: db-performance-review (complete source)

Automatic intent matching works through the frontmatter description. The name and description tell Codex the circumstances in which this review is relevant, and the Markdown body defines what to do after selection. Set allow_implicit_invocation: true or omit the policy entry (true by default). There is no file-change event handler in the skill frontmatter.

.agents/skills/db-performance-review/SKILL.md — implicit review workflow

---
name: db-performance-review
description: Review database query, SQL migration, ORM query, or schema changes for performance regressions. Use when tasks involve editing or reviewing .sql or .prisma files, database schemas, indexes, query builders, ORM repositories, or data access paths; assess query plans, indexes, cardinality, locks, and migration safety.
---

# Database Performance Review

## Input contract

Accept a planned or completed change to SQL queries, migration files, Prisma schemas, ORM query builders, or data access modules. Do not treat every code edit as database work.

## Procedure

1. Read repository instructions (`AGENTS.md`) and relevant schema and query files. Determine the database engine and deployment context; do not assume PostgreSQL merely because the example checks PostgreSQL-style SQL.
2. Find changed database files using `git diff --name-only`, `git status --short`, and the user-specified target. Do not overwrite unrelated changes.
3. Run the repository's deterministic check, if present:
   `python3 .codex/hooks/db_performance.py --check-all`
   Treat this as an anti-pattern scan, **not** as proof of a fast query. Report if the checker or runtime is unavailable.
4. For each material query change, inspect filters, joins, sorting, pagination, returned column count, N+1 risks, parameterization, cardinality, and index coverage.
5. For schema and migration changes, inspect lock duration, backfill strategy, index creation, uniqueness checks, transactional behavior, deployment compatibility, and rollback or forward-fix options. Do not execute migrations as part of a review.
6. When a suitable non-production database is available **and the user authorizes database access**, obtain a query plan with `EXPLAIN (FORMAT JSON)` using representative parameters; verify statistics and consider row-estimate errors. Obtain actual timings with `EXPLAIN ANALYZE` only when safe and separately authorized. Never run explain-analyze on a write statement against production.
7. Compare plans or benchmark distributions when a real baseline exists; otherwise describe risk hypotheses and the exact evidence still needed. Never invent speedups or execution times.
8. If performance risks are material, propose a minimal query/index/schema fix, test it, and report whether the data supports the change.

## Risk and confidentiality rules

- Never print connection strings, passwords, literal customer records, or full production query parameters.
- Do not bypass sandbox approvals or silently access an external database.
- Never introduce a production migration solely to satisfy a heuristic warning.
- Do not claim that a static regex scanner proves index usage, actual latency, or overall query efficiency.
- Distinguish advice from mandatory project policy and label unverified hypotheses clearly.

## Output contract

Return a compact review with: **Changed paths**, **Findings and severity**, **Indexes/plans needed**, **Commands run and outcomes**, and **Recommended follow-ups**. If the review was automatically requested by a lifecycle hook, focus on the file(s) flagged by that event.

.agents/skills/db-performance-review/agents/openai.yaml — automatic intent consideration

interface:
  display_name: "DB Performance Review"
  short_description: "Check database changes for query and schema risks"
  default_prompt: "Use $db-performance-review to inspect the changed SQL, ORM, and schema files for performance risks."
policy:
  allow_implicit_invocation: true

Prompts to test dynamic selection

We changed our Prisma schema and added an index.
Review the migration for locking and query-plan regressions.
Please optimize the SQL query in db/queries/list-active-users.sql.
Check filters, indexes, sorting, and pagination.
Change the button color in src/components/Header.tsx.

In the first two prompts, Codex should be encouraged by the description to choose db-performance-review; the third should not match. This remains a model selection, not a hard enforcement guarantee. If you need guaranteed static checks for certain file writes, use the hook in the next section.

6. Deterministic database change check with Codex hooks

The starter includes a real SessionStart + PostToolUse implementation rather than pretending the skill itself can subscribe to filesystem events.

How the hook works

  1. A trusted SessionStart handler inventories tracked and untracked DB-related files and stores SHA-256 hashes inside Git metadata (no repository pollution).
  2. Codex calls a supported tool. The matching PostToolUse handler runs synchronously after the tool finishes.
  3. The Python script inventories relevant files again and compares hashes with the previous snapshot.
  4. If no tracked DB-related file changed, it exits silently.
  5. For a detected change, it runs static SQL anti-pattern detection (e.g., SELECT *, leading-wildcard LIKE, ORDER BY RANDOM()) and sends concise additional context to Codex. The script is advisory by default.
  6. The optional CI job runs the checker with --strict, failing the PR for flagged SQL anti-patterns.

This design tracks *.sql, *.prisma, and selected database/queries/repository source modules. Update is_db_path() to reflect your actual organization. The static checker cannot determine real query latency or index usage; that needs representative query plans and benchmarks from an approved database.

Event boundaries: Hooks work only for supported local tool events while a trusted, enabled Codex session is running. They do not observe arbitrary external editor saves in real time, cannot undo a completed tool call, and are not a substitute for CI or database-specific benchmarking. If all writes must be monitored regardless of source, use a filesystem watcher or an editor-native watcher plus CI.

6.1 Hook configuration

.codex/hooks.json — actual hook event registration

{
  "description": "Database change detection and static performance review for Codex local sessions.",
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|resume|clear|compact|fork",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/db_performance.py\" --seed",
            "timeout": 30,
            "statusMessage": "Initializing database change detection"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash|apply_patch|Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/db_performance.py\" --post",
            "timeout": 45,
            "statusMessage": "Checking database file changes"
          }
        ]
      }
    ]
  }
}

Why the matcher is Bash|apply_patch|Edit|Write: Codex maps shell/unified-exec commands to Bash. Its apply_patch path may be matched by the canonical name or Edit/Write aliases. The matcher filters tool names, not filenames. File selection happens in our Python script. For MCP-backed edits, add the actual MCP tool name to the matcher after checking the installed hook input schema. Not all tool paths support hooks.

Trust: Codex reviews new or changed non-managed hooks before running them. From an interactive Codex session, use /hooks to inspect and trust/reject handlers. Never bypass hook trust casually; a repository hook can execute arbitrary local code.

6.2 Full hook implementation

The following is working Python 3 standard-library code. The source is included in the accompanying starter archive so you can copy it as a file without extracting it from this guide.

.codex/hooks/db_performance.py — hash-based change tracking and static checks

#!/usr/bin/env python3
"""Codex database-change guardrail, Python standard library only.

--seed: Snapshot database-related files on SessionStart.
--post: Compare the snapshot on PostToolUse; inspect only changed files.
--check-all: Scan all database-related files (manual/advisory or --strict).
--ci --base SHA: Scan only DB-related files changed in a pull request.

This is a *static anti-pattern check*. It is not an EXPLAIN or runtime benchmark.
"""

from __future__ import annotations

import argparse
from contextlib import contextmanager
import hashlib
import json
import os
from pathlib import Path, PurePosixPath
import re
import subprocess
import sys
import tempfile
import time

try:
    import fcntl  # POSIX/macOS; when unavailable, atomic lockfile is used instead.
except ImportError:
    fcntl = None


RULES = (
    (
        "SELECT_STAR",
        re.compile(r"\bSELECT\s+\*(?=\s|,)", re.IGNORECASE),
        "Avoid SELECT * in frequently called queries; select needed columns.",
    ),
    (
        "LEADING_WILDCARD",
        re.compile(r"\b(?:LIKE|ILIKE)\s+['\"]%", re.IGNORECASE),
        "A leading-wildcard LIKE often cannot use a normal B-tree index.",
    ),
    (
        "ORDER_BY_RANDOM",
        re.compile(r"\bORDER\s+BY\s+(?:RANDOM|RAND)\s*\(", re.IGNORECASE),
        "ORDER BY RANDOM()/RAND() is often expensive for large results.",
    ),
)

CODE_EXTENSIONS = {".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".kt"}
DB_DIRS = {"db", "database", "queries", "query", "prisma", "migrations", "migration", "repositories"}


def is_db_path(path: str) -> bool:
    """Conservative file-path classifier; adapt for each repository."""
    parts = PurePosixPath(path.replace("\\", "/")).parts
    if not parts or any(part.startswith(".") for part in parts):
        return False
    name = parts[-1].lower()
    suffix = PurePosixPath(name).suffix.lower()
    if suffix in {".sql", ".prisma"}:
        return True
    if suffix not in CODE_EXTENSIONS:
        return False
    if any(p.lower() in DB_DIRS for p in parts[:-1]):
        return True
    return any(token in name for token in (".query.", ".queries.", ".repository.", ".schema."))


def sql_issues(text: str) -> list[dict[str, object]]:
    """Return advisory issues with line numbers and inline suppression support.

    To suppress a known false positive, put the following on the same or
    immediately preceding line, with review justification in the PR:
    -- codex-perf-ignore: SELECT_STAR
    """
    lines = text.splitlines()
    results: list[dict[str, object]] = []
    for rule_name, pattern, explanation in RULES:
        for match in pattern.finditer(text):
            line_number = text.count("\n", 0, match.start()) + 1
            current = lines[line_number - 1] if line_number <= len(lines) else ""
            previous = lines[line_number - 2] if line_number >= 2 else ""
            marker = f"codex-perf-ignore: {rule_name}"
            if marker in current or marker in previous:
                continue
            results.append({"rule": rule_name, "line": line_number, "advice": explanation})
    return sorted(results, key=lambda issue: (int(issue["line"]), str(issue["rule"])))


def git_run(cwd: Path, *args: str) -> bytes:
    return subprocess.check_output(
        ["git", "-C", str(cwd), *args], stderr=subprocess.DEVNULL
    )


def git_root(cwd: Path) -> Path:
    return Path(os.fsdecode(git_run(cwd, "rev-parse", "--show-toplevel")).strip()).resolve()


def git_dir(root: Path) -> Path:
    path = Path(os.fsdecode(git_run(root, "rev-parse", "--git-dir")).strip())
    return (root / path).resolve() if not path.is_absolute() else path.resolve()


def tracked_and_untracked(root: Path) -> list[str]:
    out = git_run(root, "ls-files", "--cached", "--others", "--exclude-standard", "-z")
    paths = {os.fsdecode(p) for p in out.split(b"\0") if p}
    return sorted(path for path in paths if is_db_path(path))


def digest(path: Path) -> str:
    hash_obj = hashlib.sha256()
    with path.open("rb") as handle:
        while True:
            part = handle.read(1024 * 1024)
            if not part:
                break
            hash_obj.update(part)
    return hash_obj.hexdigest()


def snapshot(root: Path) -> dict[str, str]:
    found: dict[str, str] = {}
    for relative in tracked_and_untracked(root):
        path = root / relative
        if path.is_file() and not path.is_symlink():
            found[relative] = digest(path)
    return found


def state_path(root: Path, session: str) -> Path:
    # Hash session IDs so unexpected characters can't escape the git metadata directory.
    token = hashlib.sha256(session.encode("utf-8")).hexdigest()[:24]
    return git_dir(root) / "codex-db-hooks" / f"{token}.json"


@contextmanager
def locked(path: Path):
    """Synchronize parallel tool-completion callbacks for one session."""
    path.parent.mkdir(parents=True, exist_ok=True)
    if fcntl is not None:
        with (path.parent / (path.name + ".lock")).open("a+b") as handle:
            fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
            try:
                yield
            finally:
                fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
        return
    lockfile = path.parent / (path.name + ".lock")
    fd = None
    for _ in range(100):
        try:
            fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
            break
        except FileExistsError:
            # Recover abandoned lockfiles on non-POSIX hosts.
            if lockfile.exists() and time.time() - lockfile.stat().st_mtime > 120:
                lockfile.unlink(missing_ok=True)
            time.sleep(0.05)
    if fd is None:
        raise TimeoutError(f"Could not lock {path}")
    try:
        yield
    finally:
        os.close(fd)
        lockfile.unlink(missing_ok=True)


def write_state(path: Path, content: dict[str, str]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp_name = ""
    try:
        with tempfile.NamedTemporaryFile(
            mode="w", encoding="utf-8", dir=path.parent, prefix="state-", delete=False
        ) as handle:
            json.dump(content, handle, sort_keys=True)
            tmp_name = handle.name
        os.replace(tmp_name, path)
    finally:
        if tmp_name and os.path.exists(tmp_name):
            os.unlink(tmp_name)


def read_event() -> dict[str, object]:
    raw = sys.stdin.read()
    if not raw.strip():
        return {}
    event = json.loads(raw)
    if not isinstance(event, dict):
        raise ValueError("Hook event must be a JSON object")
    return event


def read_issue_list(root: Path, paths: list[str]) -> list[tuple[str, dict[str, object]]]:
    all_issues: list[tuple[str, dict[str, object]]] = []
    for relative in paths:
        path = root / relative
        if path.suffix.lower() != ".sql" or not path.is_file():
            continue
        text = path.read_text(encoding="utf-8", errors="replace")
        for issue in sql_issues(text):
            all_issues.append((relative, issue))
    return all_issues


def format_issues(issues: list[tuple[str, dict[str, object]]]) -> list[str]:
    return [
        f"{path}:{issue['line']} [{issue['rule']}] {issue['advice']}"
        for path, issue in issues
    ]


def seed(root: Path, event: dict[str, object]) -> int:
    session = str(event.get("session_id") or "manual")
    path = state_path(root, session)
    with locked(path):
        write_state(path, snapshot(root))
    return 0


def post(root: Path, event: dict[str, object]) -> int:
    session = str(event.get("session_id") or "manual")
    path = state_path(root, session)
    with locked(path):
        current = snapshot(root)
        if not path.exists():
            write_state(path, current)
            print(json.dumps({
                "hookSpecificOutput": {
                    "hookEventName": "PostToolUse",
                    "additionalContext": "DB file tracking initialized without SessionStart baseline; the first earlier edit cannot be determined."
                }
            }))
            return 0
        previous = json.loads(path.read_text(encoding="utf-8"))
        write_state(path, current)
    changed = sorted(
        path for path in (set(previous) | set(current))
        if previous.get(path) != current.get(path)
    )
    if not changed:
        return 0  # Silent after unrelated tool calls.
    issues = read_issue_list(root, [path for path in changed if path in current])
    summary = [
        f"DB change guardrail checked {len(changed)} modified DB-related file(s):",
        *[f"- {path}" for path in changed[:20]],
    ]
    if len(changed) > 20:
        summary.append(f"- ... plus {len(changed) - 20} more")
    if issues:
        summary.append("Static SQL performance smells (verify against actual query plans):")
        summary.extend(f"- {line}" for line in format_issues(issues)[:20])
    else:
        summary.append("Static heuristics found no flagged SQL patterns; this is NOT a runtime performance pass.")
    summary.append("Consider $db-performance-review for index, migration and plan-level review.")
    payload: dict[str, object] = {
        "hookSpecificOutput": {
            "hookEventName": "PostToolUse",
            "additionalContext": "\n".join(summary),
        }
    }
    if issues:
        payload["systemMessage"] = f"Database performance guardrail found {len(issues)} advisory issue(s)."
    print(json.dumps(payload))
    return 0


def check_all(root: Path, strict: bool) -> int:
    files = tracked_and_untracked(root)
    issues = read_issue_list(root, files)
    print(f"DB performance static scan: {len(files)} tracked/untracked DB-related files.")
    if not issues:
        print("No SQL anti-patterns flagged. This is not an execution benchmark.")
    for line in format_issues(issues):
        print(line)
    return 1 if strict and issues else 0


def check_ci(root: Path, base: str, strict: bool) -> int:
    changed_raw = git_run(root, "diff", "--name-only", "--diff-filter=ACMRT", "-z", f"{base}...HEAD")
    files = sorted({
        os.fsdecode(p) for p in changed_raw.split(b"\0")
        if p and is_db_path(os.fsdecode(p))
    })
    issues = read_issue_list(root, files)
    print(f"DB performance pull-request scan: {len(files)} changed DB-related files.")
    if not issues:
        print("No SQL anti-patterns flagged. Plan inspection remains manual.")
    for line in format_issues(issues):
        print(line)
    return 1 if strict and issues else 0


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument("--seed", action="store_true", help="Record initial DB file hashes")
    mode.add_argument("--post", action="store_true", help="Check post-tool DB changes")
    mode.add_argument("--check-all", action="store_true", help="Scan all monitored DB files")
    mode.add_argument("--ci", action="store_true", help="Check changes relative to --base")
    parser.add_argument("--base", help="Merge base commit for --ci")
    parser.add_argument("--strict", action="store_true", help="Fail manual/CI scan on advisories")
    args = parser.parse_args()
    if args.ci and not args.base:
        parser.error("--ci requires --base")
    try:
        event = read_event() if (args.seed or args.post) else {}
        cwd = Path(str(event.get("cwd") or os.getcwd()))
        root = git_root(cwd)
        if args.seed:
            return seed(root, event)
        if args.post:
            return post(root, event)
        if args.ci:
            return check_ci(root, args.base, args.strict)
        return check_all(root, args.strict)
    except (subprocess.CalledProcessError, OSError, ValueError, TimeoutError) as exc:
        print(f"DB guardrail could not complete: {exc}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    raise SystemExit(main())

6.3 Advisory versus enforceable validation

The hook returns a PostToolUse additionalContext message to tell Codex what changed. When it finds a heuristic issue, it also adds a warning via systemMessage. This does not rollback a completed edit. To prevent merges, use the CI job below.

For actual database plan validation, extend this starter with an application-specific, non-production database harness. Example of an approved PostgreSQL read-only plan inspection:

BEGIN READ ONLY;
SET LOCAL statement_timeout = '5s';
EXPLAIN (FORMAT JSON)
SELECT id, email
FROM users
WHERE status = 'active'
ORDER BY created_at DESC
LIMIT 20;
ROLLBACK;

Execute only after approval against a suitable non-production database with representative data and parameter values. EXPLAIN without ANALYZE estimates a plan, it does not measure actual latency. EXPLAIN ANALYZE executes statements and should not be used casually, especially on writes or production.

7. Independent CI enforcement (complete source)

The workflow is intentionally independent of Codex invocation, hooks, session state, or agent choice. It scans only DB files changed against a PR base, uses the standard-library test suite, and exits nonzero for the three configured SQL anti-patterns. Your team can replace the heuristics with dialect-specific linting, schema validators, migration checks, or an approved plan-regression suite as your database requirements mature.

.github/workflows/db-performance.yml — GitHub Actions pull-request guardrail

name: Database performance guardrails
on:
  pull_request:
    paths:
      - '**/*.sql'
      - '**/*.prisma'
      - '**/db/**'
      - '**/database/**'
      - '**/queries/**'
      - '**/migrations/**'
      - '.codex/hooks/db_performance.py'
      - '.github/workflows/db-performance.yml'
  workflow_dispatch:

permissions:
  contents: read

jobs:
  db-static-check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout with base history
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Unit tests
        run: python3 -m unittest discover -s tests -v
      - name: Check modified DB files
        if: github.event_name == 'pull_request'
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
        run: python3 .codex/hooks/db_performance.py --ci --base "$BASE_SHA" --strict
      - name: Check all DB files on manual run
        if: github.event_name == 'workflow_dispatch'
        run: python3 .codex/hooks/db_performance.py --check-all --strict

tests/test_db_performance.py — repeatable local/CI tests

"""Fast tests for deterministic DB-change detection, no external packages."""

from __future__ import annotations

import importlib.util
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest

SCRIPT = Path(__file__).resolve().parents[1] / ".codex" / "hooks" / "db_performance.py"
spec = importlib.util.spec_from_file_location("db_performance", SCRIPT)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)


class ClassifierTests(unittest.TestCase):
    def test_database_path_detection(self):
        positives = [
            "db/queries/list-users.sql", "prisma/schema.prisma", "src/db/users.ts",
            "src/repositories/user_repository.py", "backend/migrations/001_add.sql",
            "src/user.query.ts", "queries/get-users.sql",
        ]
        negatives = ["src/components/card.tsx", "README.md", ".git/index", "node_modules/x.js"]
        for path in positives:
            with self.subTest(path=path):
                self.assertTrue(module.is_db_path(path))
        for path in negatives:
            with self.subTest(path=path):
                self.assertFalse(module.is_db_path(path))

    def test_static_sql_rule_and_suppression(self):
        bad = "SELECT * FROM users;\nSELECT id FROM users ORDER BY RANDOM();\n"
        self.assertEqual([v["rule"] for v in module.sql_issues(bad)],
                         ["SELECT_STAR", "ORDER_BY_RANDOM"])
        allowed = "-- codex-perf-ignore: SELECT_STAR\nSELECT * FROM lookup;\n"
        self.assertEqual(module.sql_issues(allowed), [])


class EventTests(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory()
        self.addCleanup(self.temp.cleanup)
        self.root = Path(self.temp.name)
        subprocess.run(["git", "init", "-q", str(self.root)], check=True)
        (self.root / "db").mkdir()
        (self.root / "db" / "get.sql").write_text("SELECT id FROM users;\n")
        (self.root / "README.md").write_text("Hello\n")
        self.session = "unit-session-ABC"

    def run_script(self, *args: str, event: dict | None = None):
        return subprocess.run(
            [sys.executable, str(SCRIPT), *args],
            cwd=self.root, input=json.dumps(event or {"session_id": self.session, "cwd": str(self.root)}),
            capture_output=True, text=True, check=False,
        )

    def test_ignores_unrelated_edits_and_detects_db_changes(self):
        seeded = self.run_script("--seed")
        self.assertEqual(seeded.returncode, 0, seeded.stderr)

        (self.root / "README.md").write_text("Changed not DB\n")
        unrelated = self.run_script("--post")
        self.assertEqual(unrelated.returncode, 0, unrelated.stderr)
        self.assertEqual(unrelated.stdout.strip(), "")

        (self.root / "db" / "get.sql").write_text("SELECT * FROM users;\n")
        changed = self.run_script("--post")
        self.assertEqual(changed.returncode, 0, changed.stderr)
        payload = json.loads(changed.stdout)
        context = payload["hookSpecificOutput"]["additionalContext"]
        self.assertIn("db/get.sql", context)
        self.assertIn("SELECT_STAR", context)
        self.assertIn("systemMessage", payload)

        repeat = self.run_script("--post")
        self.assertEqual(repeat.stdout.strip(), "")

    def test_ci_and_check_all(self):
        (self.root / "db" / "get.sql").write_text("SELECT * FROM users;\n")
        advisory = self.run_script("--check-all")
        self.assertEqual(advisory.returncode, 0, advisory.stderr)
        strict = self.run_script("--check-all", "--strict")
        self.assertEqual(strict.returncode, 1, strict.stderr)
        self.assertIn("SELECT_STAR", strict.stdout)

        subprocess.run(["git", "-C", str(self.root), "config", "user.email", "test@example.com"], check=True)
        subprocess.run(["git", "-C", str(self.root), "config", "user.name", "Test"], check=True)
        (self.root / "db" / "get.sql").write_text("SELECT id FROM users;\n")
        subprocess.run(["git", "-C", str(self.root), "add", "db/get.sql"], check=True)
        subprocess.run(["git", "-C", str(self.root), "commit", "-qm", "baseline"], check=True)
        baseline = subprocess.check_output(["git", "-C", str(self.root), "rev-parse", "HEAD"], text=True).strip()
        (self.root / "db" / "get.sql").write_text("SELECT * FROM users;\n")
        subprocess.run(["git", "-C", str(self.root), "add", "db/get.sql"], check=True)
        subprocess.run(["git", "-C", str(self.root), "commit", "-qm", "regression"], check=True)
        ci = self.run_script("--ci", "--base", baseline, "--strict")
        self.assertEqual(ci.returncode, 1, ci.stderr)
        self.assertIn("SELECT_STAR", ci.stdout)


if __name__ == "__main__":
    unittest.main()

8. AGENTS.md: global + repository instructions

AGENTS.md is the model-level orchestration layer. It should guide how Codex works and when it should consider appropriate skills. It does not register new slash commands, listen to files, or override hook trust/permissions.

8.1 Repository root AGENTS.md

AGENTS.md — project guidance

# Repository guidance for Codex

## Working agreements
- Inspect `git status --short` before edits. Preserve unrelated work.
- Follow existing project conventions and tests. Prefer minimal diffs.
- Run the narrowest relevant tests and `git diff --check` after code changes.
- Never access production databases, run destructive migrations, or add dependencies without authorization.

## Skills and database changes
- Use the `refactor` skill when I explicitly invoke `$refactor`.
- For SQL, Prisma, migration, ORM query, or schema edits, consider the `db-performance-review` skill and inspect index / query-plan implications.
- Run `python3 .codex/hooks/db_performance.py --check-all` when working on database changes. This is a heuristic scan, not a real execution benchmark.
- Explain performance claims using verified `EXPLAIN`, representative measurements, and before/after evidence. Do not invent timings.
- The `.codex/hooks.json` lifecycle hook adds an automatic static check on supported tool operations; GitHub Actions is the independent enforcement point.

8.2 Optional user-global ~/.codex/AGENTS.md

docs/optional-global-AGENTS.md — copy to your Codex home if desired

# Global Codex guidance

- Make minimal, reviewable changes.
- Preserve uncommitted work.
- Read repository AGENTS.md and nested overrides before edits.
- Avoid production database access and destructive commands without permission.
- Report commands executed, verification results, and tests not run.

8.3 Optional user configuration ~/.codex/config.toml

Use this only as a merge snippet: don’t overwrite an existing configuration file.

docs/optional-config.toml — hook feature and optional skill disablement

# Optional user-level ~/.codex/config.toml additions.
# Merge with existing config rather than overwriting it.

[features]
hooks = true

# A different entry can disable a skill without removing its files.
# [[skills.config]]
# path = "/absolute/path/to/repo/.agents/skills/refactor/SKILL.md"
# enabled = false

9. Installation and deployment: reproducible steps

9.1 Prerequisites

  • Git and Python 3 available on PATH.
  • A supported Codex CLI or IDE extension that recognizes repository .agents/skills and lifecycle hooks. Verify version with codex --version.
  • Codex installed and authenticated. One installation path is npm install -g @openai/codex; check the current installation docs for alternatives and enterprise restrictions.
  • A repository in which the provided skill and hook files can be trusted and reviewed.

9.2 Install the starter into a repository

The accompanying codex-skill-starter.zip contains the complete tree. Always inspect the archive before merging with any existing .codex/, .agents/, .github/, or AGENTS.md content.

# Work from your existing Git repository root.
git rev-parse --show-toplevel
pwd

# Unzip into a temporary directory first, inspect, then copy or merge
# the selected files into your repository. Do not blindly overwrite AGENTS.md.
unzip -l /path/to/codex-skill-starter.zip
mkdir -p /tmp/codex-skill-install
unzip /path/to/codex-skill-starter.zip -d /tmp/codex-skill-install

For manual creation without the zip, these are the top-level folders you need:

mkdir -p .agents/skills/refactor/agents
mkdir -p .agents/skills/db-performance-review/agents
mkdir -p .codex/hooks .github/workflows tests

Copy each complete source code block from Sections 4–8 into its corresponding path. The starter ZIP already contains the same files; you need not recreate them manually when using the archive.

9.2.1 Safe, conflict-aware installer (recommended)

The starter archive also includes scripts/install.py. It checks that the destination is a Git repository root, aborts before writing when any existing destination differs, and copies only necessary runtime and test files. It never overwrites project configuration.

# Run from the folder where you unpacked the starter archive.
python3 scripts/install.py --repo /absolute/path/to/my-project

# When the repo already has its own AGENTS.md, do not overwrite it.
# Merge the provided guidance manually after installation.
python3 scripts/install.py --repo /absolute/path/to/my-project --skip-agents

Complete implementation (Python 3 standard library):

#!/usr/bin/env python3
"""Install starter files into a Git repo without overwriting existing content.

Usage: python3 scripts/install.py --repo /absolute/repo/path [--skip-agents]
"""

import argparse
import filecmp
from pathlib import Path
import shutil
import subprocess
import sys

KIT = Path(__file__).resolve().parents[1]
FILES = (
    ".agents/skills/refactor/SKILL.md",
    ".agents/skills/refactor/agents/openai.yaml",
    ".agents/skills/db-performance-review/SKILL.md",
    ".agents/skills/db-performance-review/agents/openai.yaml",
    ".codex/hooks.json",
    ".codex/hooks/db_performance.py",
    ".github/workflows/db-performance.yml",
    "tests/test_db_performance.py",
    "AGENTS.md",
)


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--repo", required=True, help="Existing destination Git repository root")
    parser.add_argument(
        "--skip-agents", action="store_true",
        help="Do not install AGENTS.md (manually merge guidance into an existing file)",
    )
    args = parser.parse_args()
    target = Path(args.repo).expanduser().resolve()
    if not target.is_dir():
        parser.error(f"Repository does not exist: {target}")
    try:
        actual_root = Path(subprocess.check_output(
            ["git", "-C", str(target), "rev-parse", "--show-toplevel"],
            stderr=subprocess.DEVNULL, text=True,
        ).strip()).resolve()
    except subprocess.CalledProcessError:
        parser.error("Destination is not inside a Git repository")
    if actual_root != target:
        parser.error(f"Use Git repository root: {actual_root}")

    chosen = [name for name in FILES if not (args.skip_agents and name == "AGENTS.md")]
    conflicts = [
        name for name in chosen
        if (target / name).exists() and not filecmp.cmp(KIT / name, target / name, shallow=False)
    ]
    if conflicts:
        print("ABORTED: existing destination files differ; no changes made.", file=sys.stderr)
        for name in conflicts:
            print(f"  {name}", file=sys.stderr)
        print("Review and merge these files manually. Use --skip-agents only for AGENTS.md.", file=sys.stderr)
        return 2

    added = 0
    for name in chosen:
        destination = target / name
        if destination.exists():
            print(f"UNCHANGED {name}")
            continue
        destination.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(KIT / name, destination)
        print(f"ADDED {name}")
        added += 1
    if args.skip_agents:
        print("Remember to merge docs/optional-global-AGENTS.md or starter AGENTS.md policy where appropriate.")
    print(f"Installed {added} new file(s). Review with git status --short before committing.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

9.3 Validate the local files

python3 -m json.tool .codex/hooks.json > /dev/null
python3 -m py_compile .codex/hooks/db_performance.py
python3 -m unittest discover -s tests -v
python3 .codex/hooks/db_performance.py --check-all

git diff --check
git status --short

Optional structural validation if you have OpenAI’s skill-creator scripts installed locally:

python3 /path/to/skill-creator/scripts/quick_validate.py .agents/skills/refactor
python3 /path/to/skill-creator/scripts/quick_validate.py .agents/skills/db-performance-review

The local test suite verifies the hash-state change detector, path classifier, rule suppression, manual scan, and CI check. No database connection is required.

9.4 Load skills and hooks in Codex

  1. Start a new Codex session from the correct Git repository directory using codex (or open that repository in the Codex IDE extension).
  2. Inspect /skills or type $ to find refactor and db-performance-review.
  3. Inspect /hooks. Review the repository’s SessionStart and PostToolUse hook commands, then explicitly trust them if appropriate. Codex must permit them to execute.
  4. Use the explicit $refactor ... prompt in Section 4.
  5. Use a database-related natural-language request without $ and observe whether the agent selects db-performance-review.
  6. Make a monitored SQL edit via a supported Codex tool. Watch for the database guardrail’s context or warning.
  7. If newly installed or updated skills do not appear, restart the Codex session and confirm CODEX_HOME and Git root. Codex documents automatic skill change detection with restart as a fallback.

9.5 GitHub deployment

# Review first; add only the intended files.
git status --short
git diff --check

# Example: stage the skill and validation directories.
git add AGENTS.md .agents/skills .codex/hooks.json .codex/hooks/db_performance.py \
  .github/workflows/db-performance.yml tests/test_db_performance.py

git diff --cached --check
git diff --cached --stat

# Commit/push only when you have reviewed the changes and intend to deploy.

On pull requests, the workflow runs tests and scans the changed DB-related files. The local hook remains a developer feedback mechanism, while CI provides independent gatekeeping.

10. Optional legacy slash-style command: /prompts:refactor

Codex skills are not automatically /refactor slash commands. For installations that still support the deprecated custom prompt feature, the correct user-defined command syntax is /prompts:refactor. These prompt files live under $CODEX_HOME/prompts (typically ~/.codex/prompts), not in repository .agents/skills. This legacy adapter is local to the user.

docs/legacy-refactor-prompt.md — optional deprecated custom prompt source

---
description: Refactor a bounded code scope while preserving behavior
argument-hint: FILES=<paths> GOAL="<description>"
---

Refactor the following existing files: $FILES

Goal: $GOAL

Before editing, inspect repository AGENTS.md and, if present, follow
`.agents/skills/refactor/SKILL.md` as the implementation contract.
Preserve public APIs, behavior, error semantics, and unrelated changes.
Run available targeted tests and report exactly what was verified.

Copy and invoke:

mkdir -p ~/.codex/prompts
cp docs/legacy-refactor-prompt.md ~/.codex/prompts/refactor.md
# Restart Codex or start a new chat after changing custom prompts.

Inside the Codex composer:

/prompts:refactor FILES="src/services/userService.ts" GOAL="extract duplicated validation logic"

This command expands a prompt. It is separate from the native $refactor skill mechanism and is not a supported guarantee of a literal /refactor alias. Prefer $refactor for new workflows.

11. Deployment and testing matrix

TestActionExpected outcomeInterpretation
Skill frontmatterValidate both SKILL.md filesname and description acceptedStatic schema pass
Hook JSONpython3 -m json.tool .codex/hooks.jsonExit 0Syntax pass, not runtime trust
Hook codepython3 -m py_compile ...Exit 0Python syntax pass
Unit testspython3 -m unittest discover -s tests -vAll tests passLocal deterministic behavior pass
Explicit skill$refactor ... in a new Codex sessionRefactor workflow selectedRequires installed Codex runtime
Explicit-only policyPrompt refactor this without $Should not implicitly select refactorVerify on your CLI version
Positive implicit intentAsk to optimize query and indexdb-performance-review considered/selectedModel-dependent, not guaranteed
Negative implicit intentAsk to change a UI button colorDB skill should not be selectedModel-dependent selectivity
Hook trustOpen /hooksHook visible; review/trust neededRuntime hook source discovery
Clean tool eventCodex reads/edits unrelated fileDB hook produces no change reportPrevents irrelevant feedback
Monitored SQL editCodex modifies an .sql filePostToolUse emits DB change contextSupported tool/event check
Prisma editCodex changes schema.prismaChanged file reported; plan review suggestedStatic detection, no actual DB benchmark
Static anti-patternIntroduce SELECT * in .sqlAdvisory in local sessionHeuristic detection
CI static policyPR introduces flagged SQLCI strict job exits nonzeroIndependent gate
RestartRestart Codex after changed configNew instructions/hooks consideredTest loading lifecycle
External IDE saveEdit .sql outside CodexNo immediate Codex hook guaranteeUse CI/editor watcher as needed

11.1 Test hook behavior without Codex (isolated Git repository)

Run these commands from your target repository root; they create a disposable temporary repo and do not alter your application:

CHECKER="$PWD/.codex/hooks/db_performance.py"
TMP="$(mktemp -d)"
git -C "$TMP" init -q
mkdir -p "$TMP/db/queries"
printf 'SELECT id FROM users;\n' > "$TMP/db/queries/list.sql"

printf '{"session_id":"smoke","cwd":"%s","hook_event_name":"SessionStart","source":"startup"}\n' "$TMP" \
  | python3 "$CHECKER" --seed

printf 'SELECT * FROM users;\n' > "$TMP/db/queries/list.sql"
printf '{"session_id":"smoke","cwd":"%s","hook_event_name":"PostToolUse","tool_name":"apply_patch"}\n' "$TMP" \
  | python3 "$CHECKER" --post

# Expected JSON contains db/queries/list.sql and SELECT_STAR.
( cd "$TMP" && python3 "$CHECKER" --check-all --strict ) || true

rm -rf "$TMP"  # Only the disposable directory created above.

For a real Codex hook integration test, use a disposable repository, run codex from its root, review /hooks, then ask Codex to change db/queries/list.sql through a supported local tool. The Python unit test does not prove the hook is trusted, enabled, or delivered by a real Codex runtime.

11.2 Explicit versus implicit routing acceptance tests

Positive explicit: $refactor simplify src/helpers/strings.ts without changing behavior should use refactor.

Negative explicit-only: simplify src/helpers/strings.ts should not implicitly load an explicit-only skill on a compliant runtime. Check actual behavior; legacy versions have had bugs with explicit-only visibility.

Positive implicit: Review this SQL migration for index and locking risks should encourage db-performance-review activation.

Negative implicit: Change the text of the login button should not trigger database work.

Because implicit selection is model-driven, run multiple prompts and record which skill Codex actually used before considering the routing description production-ready.

12. Updating, rollout, rollback, and security

Updating safely

  1. Create a branch; inspect clean/dirty worktree status and preserve unrelated changes.
  2. Change SKILL.md and agents/openai.yaml together when refining routing conditions.
  3. Change .codex/hooks.json and .codex/hooks/db_performance.py together when changing event semantics. Require fresh hook trust approval when Codex asks.
  4. Add unit tests for every new path pattern, SQL heuristic, or suppression behavior.
  5. Run local validation, then test in a fresh Codex session with positive and negative prompts.
  6. Review and merge through normal PR controls; use CI and code owner approval as appropriate.
  7. Roll back by reverting the Git change, or temporarily set [[skills.config]] enabled = false for a specific local skill. Disable hooks with [features] hooks = false only as an emergency local option; this disables all hooks, not just DB checks.

Security boundaries

  • Treat any skills and hook scripts from external repositories as executable/untrusted content. Read them before granting access.
  • Skill descriptions/instructions do not elevate permission or silently authorize secrets and production access.
  • Hook trust is separate from regular task approval. Non-managed hooks may be skipped until explicitly trusted.
  • Keep local hook scripts out of secret material and don’t print database credentials or user data in hook outputs.
  • Use read-only, scoped staging database credentials for plans and tests; never assume EXPLAIN ANALYZE is side-effect-free.
  • A PostToolUse hook only reacts after a tool has run. For strict prevention, combine PreToolUse checks, code review, CI, and database permissions; not every tool path is interceptable.
  • In very large monorepos, full-content hashing after every supported tool call has performance overhead. Narrow is_db_path rules or switch to a targeted event/index strategy if needed.
  • The --strict checker intentionally enforces only the three included heuristics; compliance with those rules is not proof of performant SQL.

13. Troubleshooting

SymptomDiagnosisFix
$refactor unavailableWrong directory, missing file, stale discovery, explicit-only regressionCheck pwd, Git root, SKILL.md, /skills, then restart Codex
/refactor not recognizedNative skill isn’t a literal slash commandUse $refactor; legacy prompt is /prompts:refactor
DB skill doesn’t auto-selectDescription too vague or task does not clearly matchFront-load trigger terms; test many positive/negative prompts
Hook never runsHook untrusted, disabled, wrong config path, unsupported tool, or not a local sessionInspect /hooks, .codex/hooks.json, [features], and tool event coverage
No DB warning for first changeMissing SessionStart baseline or hook loaded lateRestart and trust hook before editing; verify in fresh session
Hook fires too oftenDatabase matcher is too broadNarrow is_db_path(); tune matcher if relevant
git not foundMissing Git executable or outside Git repoInstall Git and run from a Git worktree
Query check passes but query is slowStatic checks don’t measure database executionUse approved staging EXPLAIN, statistics, and benchmarks
CI fails on intentional SELECT *Deliberate false positiveAdd an adjacent documented -- codex-perf-ignore: SELECT_STAR and justify in review
Windows native hook command failsExample uses POSIX shell command substitutionUse WSL or adapt commandWindows to your installed shell and absolute repo path

14. Official sources and further reading

  1. Build skills — current Codex/ChatGPT documentation
  2. Custom prompts — deprecated slash-command mechanism
  3. AGENTS.md — instruction discovery and precedence
  4. Hooks — events, configuration, matchers, trust and output schemas
  5. Codex CLI — installation and execution
  6. OpenAI Codex skill-creator reference
  7. Agent Skills open specification
  8. OpenAI Codex skill repository

Validation note (2026-11-01): Both skills were checked with the available local skill validator; db_performance.py was Python-compiled and passed four local unit tests, including a fresh temporary-Git-repository hash/CI scenario. Codex itself was not installed in the generation environment, so explicit invocation, implicit selection, and hook trust/execution need the live smoke tests above in your Codex installation. No database benchmarking was performed because no approved database or query workloads were supplied.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
0
Would love your thoughts, please comment.x
()
x