---
name: Codex Implement
slug: codex-implement-2
category: AI Engineering
description: Codex Implement writes features from a spec, then runs a review loop to confirm the result. Use it for new code, Codex-driven development, and changes that should be implemented rather than reviewed or architected.
github: "https://github.com/sd0xdev/sd0x-harness/tree/main/skills/codex-implement"
language: JavaScript
stars: 188
forks: 24
install: "npx degit https://github.com/sd0xdev/sd0x-harness/tree/main/skills/codex-implement ~/.claude/skills/codex-implement"
installs_to: ~/.claude/skills/codex-implement
source_path: skills/codex-implement/SKILL.md
collection_size: 25
category_size: 3278
collection_url: "https://dirskills.com/collections/sd0xdev/sd0x-harness"
added: 2026-09-06T05:20:13.777Z
last_synced: 2026-09-06T05:20:13.777Z
canonical_url: "https://dirskills.com/skills/codex-implement-2"
---

# Codex Implement

Codex Implement writes features from a spec, then runs a review loop to confirm the result. Use it for new code, Codex-driven development, and changes that should be implemented rather than reviewed or architected.

**Install:**

```bash
npx degit https://github.com/sd0xdev/sd0x-harness/tree/main/skills/codex-implement ~/.claude/skills/codex-implement
```

## README

# Codex Implement Skill

## Trigger

- Keywords: codex implement, implement feature, codex write code, implement from spec

## When NOT to Use

- Architecture advice only (use `/codex-architect`)
- Code review (use `/codex-review-fast`)
- Bug fix (use `/bug-fix`)
- Simple one-line change (edit directly)

## Workflow

```
Parse args → Decompose → Collect context → Iterate items → Review loop → Done
                                              ↕
                                    codex → diff → confirm
                                              ↕
                                    reject/modify → § Resume
```

### Step 0: Pre-check and precondition (before any repository read)

**The index must carry no `assume-unchanged` / `skip-worktree` path before anything is dispatched**
— under the two grants this skill holds, `Bash(git:*)` and `Bash(node:*)`, and no others:

```bash
node -e '
  const { execSync } = require("child_process");
  // The REPOSITORY, not the invocation directory. `git ls-files` is path-limited to the cwd
  // subtree, so running this from `skills/` would inspect a fraction of the index and report a
  // clean tree while a flagged file sat elsewhere — and the dispatch runs Codex at the top level
  // regardless of where this skill was invoked.
  const root = execSync("git rev-parse --show-toplevel").toString().trim();
  // `git ls-files -v` tags every cached path. Measured on git 2.55.0: `H` is the ordinary cached
  // entry, `S` is skip-worktree, and `-v` LOWERCASES the tag of an assume-unchanged file — so `h`
  // is assume-unchanged and `s` is both bits. Hidden state is therefore "tag is `S`, or the tag is
  // lowercase"; testing only for lowercase misses plain skip-worktree, and testing for "not `H`"
  // over-triggers on `M`, an unmerged entry, whose remedy is finishing the merge and which neither
  // `--no-assume-unchanged` nor `--no-skip-worktree` would touch.
  const lines = execSync("git ls-files -v", { cwd: root, maxBuffer: 1 << 28 }).toString()
    .split("\n").filter(Boolean);
  const hidden = lines.filter((l) => /^[a-z] /.test(l) || l.startsWith("S "));
  const unmerged = lines.filter((l) => /^[Mm] /.test(l));
  console.log(hidden.length ? hidden.join("\n") : "(none)");
  if (unmerged.length) console.log("[UNMERGED — finish the merge first]\n" + unmerged.join("\n"));
'
```

A hidden-state tag is an `assume-unchanged` or `skip-worktree` bit, and such a path is invisible three
times over: the precondition below cannot see a local edit to it, Step 3b cannot display one, and
Step 3b's Reject would treat it as baseline-absent and run `git checkout -- <path>` — which restores
the *index* version and destroys the edit for good. That is data loss with no recovery path, so it
is a hard stop rather than an opt-out: list the flagged paths, ask the user to clear the bit
(`git update-index --no-assume-unchanged <path>` / `--no-skip-worktree <path>`) or to commit or stash
the work, and re-run this step. **Never auto-revert a tracked path whose prior contents were not
captured** — the Reject row's "no state to lose" holds only for paths the baseline could actually
see.

Then resolve the adapter through `@skills/codex-code-review/references/codex-transport.md`
§ Locator, and let any auto-install that section prescribes happen before the `git status` below —
in a consuming repository its second step *writes* the adapter into the tree, and a write after the
snapshot puts an untracked file there that the changeset this step captured does not contain. Same reason the rest of this step is ordered the way it is: a snapshot that goes stale
while the work runs describes a tree nobody is looking at.

Then `git status --porcelain --untracked-files=all --ignored`, and satisfy the precondition in
§ Step 3a before Step 1 reads anything. Step 1 parses the spec, loads the feature intent and derives the items
from files the precondition may ask the user to stash or remove; running it first leaves the plan —
and later the dispatched prompt — describing a tree that no longer exists. Three review rounds moved
this earlier twice: it began inside Step 3, then before Step 2, and neither was early enough because
each time a reader still hit a repository read first. It is a numbered step so the order is
executable rather than asserted from inside a later one.

If cleanup happens after this step for any other reason, repeat the Step 1 reads and plan
confirmation, and rebind the Step 2 context, before dispatching.

**Then pin the redactor Step 3b will run, by digest, before Codex is dispatched.** Step 3b executes
that module (`require`), and by then the tree has been through a write-capable child — so no check
made *afterwards* can establish what the module was: a child that sets `assume-unchanged` on a
tracked file and then overwrites it leaves `git status` empty, which is exactly the invisibility this
step's own probe exists to catch, and a path swapped for a symlink resolves somewhere else entirely.
Authenticating the **bytes now** and requiring the same bytes later is the only order that works.

```bash
node -e '
  const fs = require("fs"), path = require("path"), crypto = require("crypto");
  const { execSync, execFileSync } = require("child_process");
  const root = fs.realpathSync(execSync("git rev-parse --show-toplevel").toString().trim());
  const real = (p) => { try { return fs.realpathSync(p); } catch { return null; } };
  const inside = (p) => p === root || p.startsWith(root + path.sep);
  const dirs = (d) => { try { return fs.readdirSync(d, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path.join(d, e.name)); } catch { return []; } };
  // Candidates: the named active installation, the two in-repo locations, and a bounded walk of the
  // plugin tree (layouts vary: cache/<marketplace>/<plugin>/<version>/, marketplaces/.../plugins/,
  // data/<plugin>/), deduplicated by real path so one physical file is never counted twice.
  // Keep BOTH paths: the one we were given and where it really points. Classifying by the real path
  // alone is an escape - a repository-local `scripts/security-redact.js` symlinked to a file outside
  // the tree would resolve "outside" and skip git validation entirely, while still being a path the
  // child can replace. So a candidate is repository-local if EITHER form is inside, and a
  // repository-local candidate must be a regular file at the path as given, not a link to one.
  const cands = [];
  const add = (p) => { const ap = path.resolve(p); if (!cands.some((c) => c.given === ap)) cands.push({ given: ap, real: real(ap) }); };
  if (process.env.CLAUDE_PLUGIN_ROOT) add(path.join(process.env.CLAUDE_PLUGIN_ROOT, "scripts", "security-redact.js"));
  for (const rel of [".claude/scripts/security-redact.js", "scripts/security-redact.js"]) add(path.join(root, rel));
  const plugins = [];
  let frontier = [path.join(require("os").homedir(), ".claude", "plugins")], seen = 0;
  for (let d = 0; d < 6 && frontier.length && seen < 4000; d++) {
    const next = [];
    for (const dir of frontier) {
      if (++seen > 4000) break;
      for (const c of [path.join(dir, "sd0x-dev-flow", "scripts", "security-redact.js"),
                       path.join(dir, "scripts", "security-redact.js")]) {
        const rp = fs.existsSync(c) && c.split(path.sep).includes("sd0x-dev-flow") ? real(c) : null;
        if (rp && !plugins.includes(rp)) plugins.push(rp);
      }
      next.push(...dirs(dir));
    }
    frontier = next;
  }
  if (plugins.length === 1) add(plugins[0]);
  // Pick the first candidate that is a regular file and, if it lives inside the repository, is
  // tracked and unmodified. THIS check is sound here and nowhere later: the tree is clean by the
  // precondition above and no write-capable child has run yet.
  const ok = cands.find((c) => {
    let st; try { st = fs.lstatSync(c.given); } catch { return false; }
    if (!st.isFile()) return false;                       // lstat: a symlink is never a candidate
    if (!inside(c.given) && (!c.real || !inside(c.real))) return true;   // genuinely outside the tree
    const rel = path.relative(root, c.real || c.given);
    try { execFileSync("git", ["ls-files", "--error-unmatch", "--", rel], { cwd: root, stdio: "ignore" }); }
    catch { return false; }
    return execFileSync("git", ["status", "--porcelain", "-z", "--", rel], { cwd: root }).toString() === "";
  });
  if (!ok) { console.log("[STOP] no trusted security-redact.js to pin" + (plugins.length > 1 ? " (several plugin installations found - set CLAUDE_PLUGIN_ROOT)" : "")); process.exit(1); }
  console.log(ok.given);
  console.log(crypto.createHash("sha256").update(fs.readFileSync(ok.given)).digest("hex"));
'
```

Carry both lines — the absolute path and its digest — in the conversation, as you carry the
`threadId`. Step 3b takes them as its two arguments and refuses to run if the file no longer hashes
to that value.

### Step 1: Parse & Decompose

**`--spec` provided**: Read spec/request doc, extract individual items.
**Arguments without `--spec`**: Use directly as single item.
**No arguments**: Ask user for requirement, target file, reference files.

**Intent check**: identify the feature this work belongs to (from the spec, the task, or the
paths) and read `docs/features/<key>/intent-<key>.md` if it exists — its `INV-*` invariants and
Non-goals constrain every item; a planned item that contradicts one stops and asks the user
(cite the line). No identifiable feature → nothing to load; proceed.

Break into **implementation items** — each one logical unit (interface, method, endpoint), implementable in dependency order, small enough for one Codex call.

Present plan before starting:

```
| # | Item              | Target File          | Depends On |
|---|-------------------|----------------------|------------|
| 1 | Define interfaces | src/interface/x.ts   | -          |
| 2 | Core logic        | src/service/x.ts     | 1          |
| 3 | Controller/Route  | src/controller/x.ts  | 2          |

Proceed?
```

### Step 2: Collect Context (Claude, NOT Codex)

Claude researches the codebase before calling Codex:

1. Read `.claude/CLAUDE.md` (fallback `CLAUDE.md`) — tech stack, conventions, test commands
2. Read target file (if exists) and context files
3. Search similar implementations
4. Read 2-3 similar files for patterns

Summarize as `PROJECT_CONTEXT` for Codex.

### Step 3: Iterative Implementation

Implement **one item at a time**, in dependency order.

#### 3a: First item — new session

See `references/codex-prompts.md` for the full prompt template.

**Two reads, in this order, before EVERY item — 3a, every 3c, and every Step 5 dispatch alike.**
Saying "take the baseline after the precondition" was circular, since the precondition is evaluated
*against* a baseline; a reviewer caught it. They are two different reads:

1. a **pre-check** — `git status --porcelain --untracked-files=all --ignored` — which is what the
   precondition below is evaluated against;
2. once the user has resolved or accepted it, the **detector baseline**: the same status command
   plus `git rev-parse HEAD` and `git stash list`. Taking this one *after* the cleanup is what stops
   the user's own commit or stash being read as a Codex violation.

The detector baseline is what the table below compares against — 3a and each 3c alike, never once per session:
`git status --porcelain --untracked-files=all --ignored`. This skill owns the implementation lifecycle, so it owns
the baseline. Both flags matter, measured once in this checkout on 2026-08-30 as a dated example
and not a property of any tree: the bare command returned 92 entries where this returned 166. The
ratio is the point, not the numbers — `--untracked-files=all` because an untracked directory otherwise collapses to a
single line and hides its files, `--ignored` because ignored files are omitted entirely. Either
omission makes an existing path look newly created. Capturing once per session is the other half of
the same bug: rejecting item 2 against item 1's baseline can revert item 1's accepted work.
Rejection (Step 3b) reverts **only** paths absent from the current item's baseline.

**One precondition, checked BEFORE dispatching each item — 3a and every 3c alike.** Step 3b runs
*after* the Codex call, so a warning there reaches the user only once the overwrite has happened.

**The item's write set must be clean at baseline.** The prompt tells Codex to touch whatever the
item needs — tests, imports, call sites — so that set is not knowable in advance, which makes the
only checkable form of this condition the strong one: **every path in the baseline must be resolved
before dispatch** (commit, stash, or remove), or the user explicitly accepts **both** consequences,
named separately because they are different losses: this run has no working rollback at all, **and**
a modified baseline-present untracked or ignored file keeps its `??`/`!!` status, so Step 3b cannot
show it either — the "complete changeset" it promises is complete only over a tree that was clean at
baseline. Accepting the first is not accepting the second; ask for both.

Five review rounds were spent patching this branch one category at a time — staged changes, ignored
files, collapsed untracked directories, the ordering, and finally the discovery that a modified
baseline-present untracked or ignored file keeps its `??`/`!!` status and so cannot be *shown* to the
user at all, let alone restored. The diagnosis behind the current shape: git cannot give this
workflow reversibility on a dirty tree, and per-category reasoning kept finding one more category.
A single upfront condition is the thing that is actually true, so it replaced the sequence of
partial promises.

**What the condition does not cover, stated rather than implied.** This class runs Codex with
workspace write access, so nothing written here *prevents* what it does to the tree. What the
workflow can still do is **notice**, and three reviewers corrected an earlier version of this
paragraph that got the boundary wrong — it claimed staging bypasses Step 3b, which is false.
Measured:

| Operation | Visible to Step 3b? | Detected how |
|-----------|--------------------|--------------|
| **Staging** (`git add`) | **Yes** — `git diff HEAD` includes staged changes, and the status command shows the index column (`M ` rather than ` M`) | Already displayed; the Reject row stops on it |
| **Commit** | **No** — `HEAD` moves, so the *committed* changes vanish from `git diff HEAD`. Measured: after a full commit that display is empty, but a partial commit (`git commit -- <path>`) leaves everything else in it, so a non-empty diff is no evidence that nothing was committed | **Record `git rev-parse HEAD` with the baseline and compare after the item.** A moved `HEAD` means the decision was bypassed: say so and stop |
| **Stash** | **No**, for the stashed portion only — `git stash push` takes `--keep-index`, `--staged`, `--patch` and a pathspec, so anything it did not take stays visible. Same shape as the partial-commit row: what is still in the diff proves nothing about what was stashed | `git stash list` before and after — `git log` does not see a stash at all. **A changed list stops the item**, exactly as a moved `HEAD` does: show the new entries (`git stash list` and `git stash show -p <entry>`) and say that reviewed changes may have left the tree |
| `assume-unchanged` / `skip-worktree` on a tracked path | **No** — the path is absent from `git status` entirely, so neither the baseline nor Step 3b's display can see a local edit to it | The Step 0 probe above reads `git ls-files -v` and flags a tag that is `S` **or lowercase**. Measured: `h` = assume-unchanged, `S` = skip-worktree, `s` = both; `-v` lowercases only the assume-unchanged mark, so a lowercase-only test misses plain skip-worktree and a not-`H` test misdiagnoses `M` (an unmerged entry, reported separately with its own remedy). Step 0's precondition requires them cleared before any dispatch — this row used to read "nothing here detects it", which was true of `git status` and false of the index |

**Run the comparisons after every write-capable dispatch** — each item at 3a/3c and each Step 5
review-loop dispatch — not once at the end: an item that committed is only attributable to that item
if the check ran around it. The prompt forbids all three operations (`references/codex-prompts.md`), which is an **instruction,
not an enforcement** — the HEAD and stash comparisons above observe **persistent drift in those two values** — not the operation: a commit later undone inside the same item, or a stash created and popped, leaves both readings unchanged.


**Bind every placeholder before writing `prompt.md`.** The template is body-only, so nothing
evaluates an expression inside it — a `${X || 'default'}` would reach Codex as literal text. Two
have no natural empty form and this skill supplies it: `${CONTEXT_CONTENT}` becomes `None` when
there is no extra context, and `${TARGET_CONTENT}` becomes `(new file)` when the target does not
exist yet.

Dispatch per `@skills/codex-code-review/references/codex-transport.md` § Start with
**`--class implement`** — this skill is its only caller, and that class is what gives Codex
`workspace-write`. Guard 3 in `test/rules/codex-transport-guards.test.js` pins that ownership.

**The MCP era's ask-on-failure approval had no headless equivalent**, so the transport pins its own
policy instead — the value is in `@skills/codex-code-review/references/codex-transport.md` § Start, and naming it here would make this file a second
authority for it. The reason is that the exec transport is non-interactive: nobody could answer an
approval prompt, so the old policy could only hang or fail. The human control that replaced it is
**Step 3b below**, which shows the complete changeset and requires the user to accept, reject or
modify every item before the next one.

**Save the returned `threadId`.**

#### 3b: Confirm each item

After each Codex call, show the **complete** changeset, then ask the user. The changeset has two
halves and **both go through the scanner** — the single command below produces the whole display:

- **tracked modifications**, from `git diff HEAD` — `HEAD`, not a bare `git diff`, which shows only
  unstaged changes and would hide anything the item staged. Printing that diff directly was a leak
  path of its own: an item that writes a token into an existing tracked file and stages it puts the
  token in the diff, and the created-file scan never sees that file at all;
- **created paths**, from `git status --porcelain --untracked-files=all --ignored`, including
  ignored ones that `--exclude-standard` would omit. Listing filenames is not showing the changeset:
  this workflow explicitly supports new files (`${TARGET_CONTENT}` renders `(new file)`), so without
  the contents a user can accept source or test files they never saw.

**Nothing is printed until it has passed a content scan, and status alone is not the
boundary.** An earlier revision withheld ignored (`!!`) files and printed the rest, which is
half a rule: a `credentials.json` or a source file with an embedded token is routinely untracked
and *not* ignored, so a status test would have printed it. Two gates, and every printed path — a
tracked diff included — must clear both:

1. **Status.** An ignored path (`!!`) is never printed. `.gitignore` is the project's own statement
   that these files are not source, and it is where `.env`, credentials, key material and logs live —
   this repository ignores `.env` itself.
2. **Content.** Every path that *is* printed has its bytes scanned first, and the scan decides:
   high-confidence secret ⇒ withhold the file; medium-confidence ⇒ print the masked text, never the
   raw. This is what catches the non-ignored file the status gate lets through.

Both gates run under the existing `Bash(node:*)` grant, and deliberately so — this workflow adds no
