Skip to content

Codex plugin installation (experimental); shell hooks load the vault - #22

Merged
Evgenii-Konev merged 5 commits into
mainfrom
feat/codex-plugin
Oct 3, 2026
Merged

Evgenii-Konev merged 5 commits into
mainfrom
feat/codex-plugin

Conversation

@Evgenii-Konev

Copy link
Copy Markdown
Member

Lands the Codex plugin installation, still experimental, together with the two fixes its review required before it could land.

Commits

  1. feat(codex): add portable plugin installation: Codex's work, carried onto main with its tree unchanged (byte-identical to 18ff8be). It renders the 30 namespaced skills and hooks.json into adapters/codex/ and ships them in the private projectstore-codex shell, with canonical and compatibility plugin manifests. Its installer stages a marketplace under CODEX_HOME, drives Codex's CLI, and verifies the cache by version and digest.
  2. fix(codex): let shell hooks load the vault; relabel Codex experimental: closes F1 and F2 of the 2026-10-03 review.

F2: the shell's hooks could not load the vault

Measured on the real install's cache (codex-cli 0.153.4, the dev build 0.28.0-rc.2+codex.dev.15c346cc0791). The installed SessionStart hook was run the way Codex runs it, with zsh -lc and PLUGIN_ROOT set to the shell root. It failed with vault load failed — Layout not found in every session. In the first session the failure sat beneath the welcome, which was all the user saw.

The cause: pluginRoot() took the host's PLUGIN_ROOT, which is the shell root, for the core root, while the core sits beneath it in node_modules/projectstore/.

The fix: if a host root contains the running core, pluginRoot() treats that root as a shell and resolves from the core's own location. A root that equals the core, or is unrelated to it, is returned as before, so the Claude Code path is unchanged; the reviewer compared hook outputs against the parent commit.

The new test builds every shell that renders hooks from the packed core and fires every rendered hook through sh -c:

  • SessionStart three times; each run must show the vault skeleton.
  • A write to a vault story through the harness's write tool, then a PreCompact, which must name that story as in flight.

It fails on the parent commit and passes now. A unit test covers the resolver's cases: a root that contains the core, one equal to it, an unrelated one, a sibling with the same prefix, a trailing separator, and a symlink.

F1: Codex was labelled supported

  • harnesses/codex.json has verified: null again, with a verified_reason: the 2026-09-30 run exercised one skill, not the hooks.
  • docs/harnesses.md, the README tagline, the README's Codex and shells paragraphs, and the shell's README now all say experimental.
  • The harness page claims only what was measured.
  • The install command reads the version from package.json at run time instead of naming rc.2's tarball. A test refuses a versioned tarball name in any shipped document.

Verification

  • npm run release:check passes: adapters, guard, shells, 516/516 tests (514 before), and npm pack (149 files).
  • The release matrix is still ["projectstore-claude"]. The Codex shell stays private.

Still open

These are tracked in the vault story The Codex shell's plugin root: .codex-plugin, the rendered surfaces, and the first Codex install (PS-HARNESS):

  • F3, F4 and F5, and S1–S10, from the 2026-10-03 review.
  • The live Codex session that fires the hooks from an installed release. Only that run sets verified.

Render the commands, agents and skills into adapters/codex/ as 30 namespaced
skills plus hooks.json, shipped in the projectstore-codex shell with canonical
and compatibility plugin manifests. The installer stages a marketplace under
CODEX_HOME, runs Codex's CLI and verifies the cache by version and digest.

Maintainer: [email protected]
The host's PLUGIN_ROOT names the shell's root, and pluginRoot() took it for
the core's. In a shell the core sits beneath it, in node_modules/projectstore/,
so the first Codex install's hooks, run by hand the way Codex runs them, said
"vault load failed — Layout not found" in every session; in the first it sat
beneath the welcome, which was all the user saw. No test had run a rendered
hook from a built tree.

- pluginRoot(): a host root that CONTAINS the running core is a shell, and the
  core answers from its own location. A root equal to the core or unrelated to
  it is returned as named, so Claude Code is unchanged. Compared as real paths
- A shells test builds each hook-rendering shell from the packed core and fires
  every rendered hook through sh -c: three SessionStarts must orient on the
  bound vault, and a PreCompact must name the story just written. It fails
  with the parent's harness.mjs. A resolver test pins contained, equal,
  unrelated, prefix-sibling and symlinked roots
- Codex is experimental again: verified is null, with the reason beside it —
  the 2026-09-30 run exercised one skill and not the hooks. Both READMEs and
  the harness page say so, and the page claims only what was measured
- The install command reads the version from package.json instead of naming
  rc.2's tarball, and a test refuses a versioned tarball name in any shipped
  document
- 516 tests pass (from 514)

Closes F1 and F2 of the 2026-10-03 review in "The Codex shell's plugin root:
.codex-plugin, the rendered surfaces, and the first Codex install"
(PS-HARNESS). The rest of that review, and the live run that alone sets
verified, stay open there.

Maintainer: [email protected]
The previous commit's correction to docs/harnesses.md made a new claim: that
all 759 captured firings came from the inline form. The spike recorded 757
inline firings across SessionStart, UserPromptSubmit, PreToolUse, PostToolUse
and Stop, plus 2 from a file-site hooks/hooks.json. PreCompact has never been
observed firing on Codex, and neither has the ${PLUGIN_ROOT} form selected
through extensions.com.openai.

- docs/harnesses.md says that, and says the hand-run probe used zsh -lc:
  that machine's default shell, started the way Codex's runner starts a
  hook on its main branch (<default shell> -lc), unconfirmed on 0.153.4
- verified_reason names where the bar is defined (docs/harnesses.md) and
  what derives the label from it (contract 16 of the generation spec); it
  drops the validator claim, which F5 of the 2026-10-03 review in "The
  Codex shell's plugin root: .codex-plugin, the rendered surfaces, and the
  first Codex install" (PS-HARNESS) has yet to substantiate, and names what
  a live run must exercise before the block is set
- The built-shell hook test starts each hook in a temporary HOME, so only
  the payload's cwd can name the project

Maintainer: [email protected]
One session's hooks run concurrently: parallel tool calls each fire
PreToolUse. writeSessionState truncated the pointer file in place, so two
writers left the shorter JSON followed by the longer one's tail. CI run
37116511756 failed on exactly that: "Unexpected non-whitespace character
after JSON at position 139". While the file is torn, the status line reports
the session state unreadable. readSessionState answers null, so the next write
keeps only its own patch, and the rest of the pointer is lost for good.

- writeSessionState and writeAnchorOffer publish through writeFileAtomic
  (a temp file, then a rename), so a reader gets the old bytes or the new
  ones, never an empty or torn file
- The concurrency test now parses the pointer while 24 writers race, with a
  positive control. Against the old write it failed 9 of 15 local runs; with
  the fix it fails none
- A deterministic test asserts that both files are new inodes after a
  rewrite, which an in-place write cannot satisfy
- The comments that kept counters out of writeSessionState now give the
  reason that still holds: an unlocked read-modify-write loses concurrent
  updates, and the last rename wins

Maintainer: [email protected]
layout-legacy named the package's shell for every install. Run for a
git-marketplace user, the shell registered projectstore@projectstore-npm
and turned the git copy off for the checkout, so /plugin update stopped
reaching it. The maintainer decided on 2026-10-03 that the move keeps the
user's channel; the normative text is contracts 7 and 12 of "The
project-level layout: .projectstore/, harness overlays, state, and the
migration from .claude/", as amended that day.

- layoutRemedy names the move in the channel of the copy the project runs:
  the session's host-cache root, else this project's newest present enabled
  registry copy (own row first, never another checkout's; a 0.27.x copy is
  told to update first), else the running root. installChannel compares
  real paths. agents-in-binding names the same command
- Both remedy forms pass the new --no-register (plan, install, upgrade), so
  the move calls no host command by construction. A relocated host home
  rides along as a command prefix
- A registration the run leaves out sets the render root at its own row in
  the plan loop; read after the loop, it changed no item
- While the move is pending, the startup line offers the move alone, and
  the re-stamp offer needs our status-line wiring. layout-legacy names every
  legacy file that keeps it alive, the welcome marker included
- The move's cleanup removes a legacy launcher when the status-line slot is
  foreign or empty, and keeps one any settings file the host reads still
  runs, compared by file identity
- S1: publicItem strips private fields per step kind, so plan --json again
  shows the layout steps' from and files
- S2: portablePayloadRoot keeps doctor from reporting "<core> is not a
  portable plugin root" in a project that merely contains .codex/; a shell
  naming a root that is not one marks the plan incomplete
- README "Upgrading", commands/doctor.md and update_instructions give both
  forms; the README adds the way back, the agents block's placement, the
  projectstore- skill prefix and what uninstall leaves in AGENTS.md
- 530 tests pass (from 517)

Closes S1 and S2 of the 2026-10-03 review in "The Codex shell's plugin
root: .codex-plugin, the rendered surfaces, and the first Codex install"
(PS-HARNESS), which the maintainer set to land before the rc.3 tag.

Maintainer: [email protected]
@Evgenii-Konev
Evgenii-Konev merged commit 418448e into main Oct 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant