Skip to content

feat: add Codex host support#120

Closed
andrewjordancampbell wants to merge 6 commits into
garrytan:mainfrom
andrewjordancampbell:codex/codex-host-support
Closed

feat: add Codex host support#120
andrewjordancampbell wants to merge 6 commits into
garrytan:mainfrom
andrewjordancampbell:codex/codex-host-support

Conversation

@andrewjordancampbell

@andrewjordancampbell andrewjordancampbell commented Mar 17, 2026

Copy link
Copy Markdown

Summary

  • add Codex as a first-class gstack host without duplicating workflow logic
  • generate .agents/skills/gstack*/SKILL.md from the same .tmpl templates as Claude
  • make setup, browse discovery, tests, CI, and docs understand both Claude and Codex installs
  • harden the canonical Codex bootstrap path so installs land in ~/.codex/skills/ and browse resolves there first
  • derive Codex skill inventories/support links from the repo layout so future skills do not silently miss Codex wiring
  • add Codex-facing repo docs (AGENTS.md, agents/openai.yaml) and generated support links under .agents/skills/gstack

What Changed

  • scripts/gen-skill-docs.ts
    • adds --host codex
    • routes Codex output into .agents/skills/
    • injects Codex frontmatter (name, description)
    • keeps shared resolvers host-aware
    • rewrites skill invocation syntax to $gstack-* for Codex
  • shared templates
    • replace hardcoded helper-doc paths with minimal placeholders ({{SKILL_ROOT}}, {{LOCAL_SKILL_ROOT}}, {{REVIEW_ROOT}})
    • keep one template source for both hosts
  • install/runtime
    • setup now supports --host claude|codex|auto
    • bin/dev-setup and bin/dev-teardown cover the Codex support tree alongside Claude dev mode
    • Codex installs now link into ~/.codex/skills/, backed by the generated .agents/skills/ tree
    • browse/src/find-browse.ts and browse/bin/find-browse now prefer .codex, then .agents, then legacy Claude paths
    • Codex support links now sync from discovered skills instead of a hand-maintained list
  • quality gates
    • extend tests for Codex freshness/frontmatter/path safety
    • discover skills/templates dynamically in tests and skill:check, so new skills automatically get Codex coverage
    • run Codex dry-run generation in CI
  • docs
    • add AGENTS.md
    • add agents/openai.yaml
    • update README/CONTRIBUTING for dual-host development
    • add user-facing CHANGELOG entry for Codex support

Testing

bash -n setup bin/dev-setup bin/dev-teardown
bun test
bun run gen:skill-docs --dry-run
bun run gen:skill-docs --host codex --dry-run
bun run skill:check
TMP_HOME=$(mktemp -d)
HOME="$TMP_HOME" ./setup --host codex
HOME="$TMP_HOME" "$TMP_HOME/.codex/skills/gstack/browse/bin/find-browse"

Pre-Landing Review

Pre-Landing Review: No issues found.

Notes

  • Codex skills are generated only. Do not hand-edit .agents/skills/*/SKILL.md.
  • Codex-generated markdown intentionally contains no .claude/skills paths.
  • The root Codex skill directory includes support symlinks (bin, browse, review, setup, etc.) so helper-doc lookups and browse setup work after ./setup --host codex.
  • .agents/skills/ remains the generated source tree in the repo; ~/.codex/skills/ is the active installed skill location.

Andrew Campbell and others added 6 commits March 16, 2026 23:50
Add host-aware skill generation so the shared templates produce Codex-native skill files under .agents alongside the existing Claude output.
Teach the install and development helpers how to wire Codex skill directories, and prefer .agents paths when locating the browse binary.
Extend the skill generation and validation checks so Codex outputs are exercised in tests and CI.
Document the Codex install path, contributor workflow, and repository metadata for the new host.
Co-Authored-By: GPT-5-Codex <noreply@openai.com>
Make ~/.codex/skills the canonical Codex install root, teach browse discovery to prefer it consistently, and update generated docs/tests so the bootstrap path works out of the box.

@andrewjordancampbell andrewjordancampbell left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: feat: add Codex host support

Findings

1. pr-body.md committed to repo root

  • Severity: Non-blocking
  • Location: pr-body.md (new file, 56 lines)
  • Description: This file is a copy of the PR description committed as a standalone file in the repo root. PR descriptions are GitHub artifacts preserved in PR history — committing them as files creates maintenance burden and clutters the repo root.
  • Suggested Fix: Remove pr-body.md from the commit. The PR description on GitHub is the canonical source.

2. --host as last CLI argument produces unclear error

  • Severity: Non-blocking
  • Location: scripts/gen-skill-docs.ts — HOST argument parsing
  • Description: If a user runs bun run gen:skill-docs --host (without a value), process.argv[HOST_ARG_INDEX + 1] evaluates to undefined. The validation catches this, but the error message reads Invalid --host value: undefined — a dedicated check for a missing value would be clearer.
  • Suggested Fix: Check for undefined before the general validation and throw --host requires a value: "claude" or "codex".

3. setup auto mode silently no-ops when neither host is detected

  • Severity: Non-blocking
  • Location: setup — auto mode case branch
  • Description: When --host auto is used and neither $HOME/.codex nor $HOME/.claude directories exist, the script completes silently without creating any symlinks and without warning the user.
  • Suggested Fix: Add a fallback warning when no host environment is detected.

4. link_codex_global() doesn't validate SKILL.md presence

  • Severity: Non-blocking
  • Location: setuplink_codex_global() function
  • Description: link_claude_local() and link_claude_global() both check [ -f "$skill_dir/SKILL.md" ] || continue before creating symlinks, but link_codex_global() only checks [ -d "$skill_dir" ] || continue. Non-skill directories matching the gstack* glob would be symlinked.
  • Suggested Fix: Add the same [ -f "$skill_dir/SKILL.md" ] || continue guard to link_codex_global().

5. find-browse TypeScript uses existsSync but bash uses -x (executable check)

  • Severity: Non-blocking
  • Location: browse/src/find-browse.ts vs browse/bin/find-browse
  • Description: The bash script checks executability (-x), while the TypeScript implementation only checks existence (existsSync). A non-executable binary file would be returned by TypeScript but skipped by bash.
  • Suggested Fix: Use fs.accessSync(candidate, fs.constants.X_OK) in the TypeScript implementation, or document this as intentional.

6. Invocation rewriting transforms content inside code blocks

  • Severity: Non-blocking
  • Location: scripts/gen-skill-docs.tsreplaceInvocations()
  • Description: The regex operates on full template content including fenced code blocks. A literal /browse inside a code example would be transformed to $gstack-browse. Currently doesn't cause issues in existing templates, but is fragile for future template authors.
  • Suggested Fix: Consider skipping replacement inside fenced code blocks, or accept as intentional since Codex skills should always show $gstack-* syntax.

7. Test coverage gap: no explicit test for invocation syntax transformation

  • Severity: Non-blocking
  • Location: test/gen-skill-docs.test.ts
  • Description: Tests verify Codex output doesn't contain .claude/skills paths, but there's no explicit test verifying /browse is transformed to $gstack-browse. This is a core feature of the Codex generation pipeline.
  • Suggested Fix: Add a test verifying $gstack-* syntax appears and /skill syntax does not in Codex output.

Summary

Overall assessment: Ready to merge. No blocking issues found.

This is a well-architected PR that adds Codex as a first-class host without duplicating workflow logic. The key design decisions are sound:

  • Single template source with host-aware placeholder resolution keeps the two hosts in sync
  • Relative symlinks in .agents/skills/gstack/ correctly point back to repo root (3 levels up)
  • Path precedence (.codex > .agents > .claude) is consistently implemented across bash and TypeScript
  • CI validation runs both --dry-run and --host codex --dry-run to catch staleness
  • Path isolation is enforced — tests verify no .claude/skills paths leak into Codex output
  • replaceInvocations() uses proper regex with word boundaries to avoid false matches
  • Output directories are created with mkdirSync({ recursive: true }) before writing

The 7 non-blocking findings are minor improvements around edge case handling, test coverage, and a stray pr-body.md file. None affect correctness for the intended use cases.

@garrytan

Copy link
Copy Markdown
Owner

7K+ lines, massive and stale. Closing.

@andrewjordancampbell

Copy link
Copy Markdown
Author

Follow-up hardening is now pushed.

The main issue I found was install/discovery mismatch for Codex, not the overall direction. This update makes ~/.codex/skills the canonical Codex install root, makes browse discovery prefer that path consistently, and keeps .agents/skills as the generated source tree in-repo.

Re-verified:

  • bun test
  • bun run gen:skill-docs --dry-run
  • bun run gen:skill-docs --host codex --dry-run
  • bun run skill:check
  • fresh HOME smoke test for ./setup --host codex

anbangr added a commit to anbangr/gstack that referenced this pull request May 20, 2026
…iting "landed" (#61)

The daemon used to classify a sub-agent result based ONLY on exit code:
exit 0 = "landed", non-zero/timed_out = "blocked". But the /land-and-deploy
sub-agent (Kimi / Claude / Codex) gracefully declines to merge in real
scenarios — failing CI, no PR found, pre-merge gate fails — and still
exits 0 (subprocess ran cleanly, verdict lives in prose).

Observed in production on 2026-05-19 (mitosis-control-plane release-queue):

  Queue says    GitHub reality
  PR garrytan#120 landed  OPEN, e2e failing
  PR garrytan#122 landed  MERGED ✓
  PR garrytan#124 landed  MERGED ✓
  PR garrytan#129 landed  OPEN, e2e failing

50% false-positive rate. The queue records are stuck on "landed" so
discoverQueuedRecords (which filters status === "queued") never re-fans
out for them — silently abandoned.

Fix: after the land sub-agent returns exit 0, ask GitHub for the
authoritative PR state. New helper `verifyPrMerged(prNumber, repoIdentity,
cwd)` runs `gh pr view <pr> --repo <owner/name> --json state -q .state`
and returns { merged: true } only on MERGED. Anything else (OPEN, CLOSED,
gh exited non-zero, repoIdentity unparseable) returns { merged: false,
reason: "..." } and the daemon writes "blocked" with a useful lastError.

The repoIdentity is parsed via a strict ^github.com/owner/repo$ regex
so a planted record can't sneak shell-specials through to gh — anything
that doesn't match the regex gets rejected before any subprocess runs.

Network/auth failures (gh exited non-zero) are reported as not-merged
rather than thrown — better to block the record and retry than crash
the daemon and leak the lock. The remediation for any blocked record
is the existing retryReleaseQueueRecord CLI.

Injection seam: opts.verifyMerged?: typeof verifyPrMerged so tests can
control the response. Defaults to the real verifyPrMerged.

Scope: pre-existing bug, not a regression from PR #57. PR garrytan#120 was
marked landed at 12:30Z on 2026-05-19, hours before PR #57's work
began. PR #57's deploy verification surfaced it by running through a
real PR (garrytan#129) with failing e2e.

Tests: 60/60 release-daemon.test.ts (5 new regressions covering the
production bug pattern, the happy path, network failures, identity
parsing, and the positive parse case). Full orchestrator suite: 1525
pass / 16 fail, all 16 pre-existing on origin/main (confirmed via
git stash).

Remediation for stranded records on production right now (PR#120,
PR#129 on mitosis-control-plane): once this lands, the daemon needs
manual revival of those two records. The retryReleaseQueueRecord CLI
only handles blocked→queued, not landed→queued. Suggest a one-shot
audit script or extending the retry CLI to support landed records
whose GitHub state disagrees. Out of scope for this PR.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants