Skip to content

feat: codex-only ctm — drop claude, daemon, and web UI#23

Merged
aksOps merged 13 commits into
mainfrom
worktree-codex-default
May 15, 2026
Merged

feat: codex-only ctm — drop claude, daemon, and web UI#23
aksOps merged 13 commits into
mainfrom
worktree-codex-default

Conversation

@aksOps

@aksOps aksOps commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

ctm pivots from a Claude-Code-specific tmux wrapper to a CLI-only codex
session manager. Three axes of change:

  1. Codex becomes the only built-in agent. Adds the codex Agent
    implementation (internal/agent/codex/) with post-spawn thread-UUID
    discovery so reattach uses codex resume <uuid> instead of --last.
    Deletes internal/agent/claude/. Sessions schema bumps to v3,
    migrating any legacy agent="claude" rows to agent="codex".

  2. ctm serve HTTP daemon and the React web dashboard are gone
    completely.
    All of internal/serve/, cmd/serve.go, cmd/auth.go,
    the ui/ tree, and the Makefile UI pipeline come out — net
    −44k lines. ctm now binds no ports and ships no embedded assets.
    Hook fan-out via ~/.config/ctm/config.json hooks map remains
    (in-process via internal/hooks).

  3. Codex equivalents replace claude-only ergonomics. YOLO swaps
    --dangerously-skip-permissions for --sandbox danger-full-access.
    Doctor's required-binaries list flips claude → codex. config.json
    schema v2 rewrites required_in_path: [\"claude\", …][\"codex\", …]
    on first run, preserving any user additions. Statusline / overlay
    / PostToolUse-log subcommands are removed (no codex equivalent).

The five feat(codex): 0N foundation commits already in the branch
(interface, registry, schema v2, default-agent guard, claude impl)
were a no-claude-yet stepping stone — this PR builds on them and
finishes the migration.

Verification

  • go build ./... — green
  • go test ./...336 / 336 pass across 19 packages
  • govulncheck ./... — 0 user-code vulnerabilities
  • go test -race ./... — green
  • Smoke test: built binary + temp HOME with legacy v1 config containing
    \"claude\"ctm doctor runs the migration and reports
    required_in_path: codex, node, go, bun and schema_version: 2
    on disk. Codex resolved at /home/dev/.nvm/.../bin/codex. None of
    serve, auth, overlay, statusline, logs, log-tool-use
    appear in the command list.

Test plan

  • make regression from a fresh checkout (build + test + race + govulncheck)
  • ctm yolo <name> with codex on $PATH — confirm tmux session
    created, codex spawns inside, sessions.json populated with
    agent: codex and agent_session_id filled within ~5s of spawn
  • Detach + ctm (default attach) — confirm reattach uses
    codex resume <agent_session_id> (visible in pane shell history)
  • Existing user with ~/.config/ctm/config.json schema v1 +
    required_in_path: [\"claude\", …] — confirm first invocation
    after upgrade migrates to v2 and rewrites claude → codex
  • Existing user with sessions.json schema v2 + agent: \"claude\"
    rows — confirm migration to v3 + rewrite to codex
  • ctm doctor shows [OK] codex instead of [OK] claude in the
    Dependencies section
  • (Manual) Click Save (and continue) 🤖 on
    bestpractices.dev project 12716
    to refresh the badge with the post-codex-pivot
    .bestpractices.json

Breaking changes

  • ctm serve, ctm auth, ctm overlay, ctm statusline, ctm logs,
    ctm log-tool-use subcommands removed.
  • HTTP API (/api/*), SSE event bus, webhook delivery, allowed-origins
    file, single-user auth — all gone.
  • Web UI removed; no embedded assets.
  • claude CLI no longer supported as the pane process; codex required.

🤖 Generated with Claude Code

aksOps and others added 13 commits May 14, 2026 15:06
Adds the Agent interface and a thread-safe in-process registry. Each
agent CLI (claude, codex, future opencode) will implement Agent in its
own package and call agent.Register() from init().

This commit defines the abstraction only; no agents are registered yet
and no cmd/* code consumes it. Subsequent tasks (02-06) relocate
internal/claude/ under internal/agent/claude/ and make it implement
Agent, then refactor cmd/* to call agent.For(sess.Agent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure rename: internal/claude → internal/agent/claude. Package name
stays `claude` (Go decouples package name from directory path).
Import paths updated in cmd/attach.go, internal/health/claude_check.go,
and internal/session/spawn.go.

No behavior change. All 895 tests pass; build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps sessions.json schema_version 1 → 2. Adds two Session fields:

- Agent: immutable per-session tag identifying the driving CLI
  (claude | codex | future opencode). Set at creation; never
  mutated. Empty value on read normalizes to "claude" via the new
  Session.NormalizeAgent() helper.
- AgentSessionID: opaque per-agent backend session/thread ID. For
  claude == UUID. For codex it is the thread UUID discovered post-
  spawn from the rollout file (Task 12).

Migration step stampAgentClaude backfills agent="claude" on rows
missing the field. Idempotent and stable against repeat runs.

Save() normalizes empty Agent to "claude". Strict registry-based
unknown-agent rejection is deferred to Task 06, after claude has
registered itself with the agent.For() lookup table.

112 session tests pass (4 new); 899 total in 28 packages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the direct TestNormalizeAgent_DefaultsClaude test covering the
read-side helper landed in Task 03. Verifies:
  - zero-value Session.Agent normalizes to "claude"
  - explicit "codex" passes through
  - forward-compat: any non-empty value passes through (e.g., a
    future "opencode" entry)

No production code change — cmd/* read paths will start calling
NormalizeAgent() in Task 06's refactor. Test docs the contract those
callers rely on.

900 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/agent/claude/claude.go wraps the existing BuildCommand,
OverlayPathIfExists, and process-discovery helpers in an Agent
implementation registered via init().

The interface methods all delegate to the unchanged package-level
functions — no behavior change. Anything that links this package now
gets agent.For("claude") working out of the box.

CTM_CLAUDE_BIN env var lets integration tests override the binary
path (matches the pattern documented for codex in agent.go).

7 new claude tests; 907 total pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds OpenAI's `codex` CLI as a first-class Agent implementation alongside
the existing claude impl, plus the post-spawn thread-UUID discovery flow
that lets future reattach use `codex resume <uuid>` instead of `--last`.

- internal/agent/codex/: Agent impl, BuildCommand (codex resume <id>
  with `|| codex` fallback, --sandbox danger-full-access for yolo),
  process check, and DiscoverSessionID polling ~/.codex/sessions/
  <YYYY>/<MM>/<DD>/rollout-…-<uuid>.jsonl files newer than spawnStart.
  16 unit tests cover registration, BuildCommand shapes, env-export
  prefix, shell quoting, and discovery (happy path, pre-spawn ignored,
  newest-wins, missing dir, non-rollout files).

- internal/procscan/: shared /proc walker (FindChild + IsAlive)
  extracted so internal/health and per-agent process.go files don't
  duplicate the logic.

- internal/health/agent_check.go: agent-agnostic CheckAgentProcess(tc,
  sessionName, procName) — supersedes the claude-only CheckClaudeProcess.

- internal/agent/agent.go: Agent interface gains DiscoverSessionID
  method. SpawnSpec docs updated to describe codex semantics.

- main.go: blank import internal/agent/codex so the binary registers
  the codex agent at link time.

- internal/session/spawn_discovery_test.go: end-to-end test of the
  post-spawn goroutine (real Store + fake tmux + simulated rollout
  file). Uses OnDiscoveryComplete callback for deterministic sync.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switches the on-disk + in-memory default agent from claude to codex,
adds the v2→v3 migration that rewrites legacy claude rows, and wires
the post-spawn UUID discovery into Store.Save via a background
goroutine.

- internal/session/state.go:
  * SchemaVersion bumped to 3.
  * DefaultAgent constant = "codex".
  * NormalizeAgent and Save both remap empty + legacy "claude" → codex
    so a stale in-memory Session never surfaces as an agent.For miss.
  * New rewriteClaudeToCodex migration step (v2→v3) for on-disk rows.
  * New Store.UpdateAgentSessionID(name, id) — atomic, locked,
    idempotent — for the discovery flow to stamp results.

- internal/session/spawn.go:
  * Yolo dispatches via agent.For(opts.Agent) (DefaultAgent on empty)
    instead of importing internal/agent/claude directly.
  * SpawnOpts gains Agent + OnDiscoveryComplete fields.
  * After tmux create + Store.Save, fires a discoverAndStamp goroutine
    when the Store also implements AgentSessionStamper. The goroutine
    runs Agent.DiscoverSessionID and stamps the result; failures and
    timeouts log at slog.Debug only — discovery is opportunistic, not
    load-bearing.

- Tests rewritten for the new defaults: TestNormalizeAgent_DefaultsCodex
  (covers empty + legacy "claude" remap + forward-compat passthrough),
  TestSession_EmptyAgentDefaultsCodexOnSave,
  TestSession_LegacyClaudeRewrittenOnSave,
  TestMigration_V1ToV3_RewritesAllToCodex (full v1→v2→v3 path),
  TestMigration_V2ToV3_RewritesClaudeRowsOnly (isolated v2→v3 step
  preserves non-claude agents). spawn_test.go gets a blank import of
  internal/agent/codex so Yolo can resolve "codex" in the registry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… serve callsites

Replaces direct imports of internal/agent/claude in the cmd/* layer
with agent.For() lookups, swaps every "claude" default-name literal
for session.DefaultAgent (codex), and removes the daemon coupling
(proc.EnsureServeRunning + fireServeEvent) that the upcoming
internal/serve removal will deprecate. Tests follow the same swap.

- cmd/attach.go: drops internal/serve/proc + internal/agent/claude
  imports; uses agent.For(sess.NormalizeAgent()) to resolve the
  per-session impl; passes the resolved Agent's BuildCommand on
  recreate/respawn; CheckAgentProcess via the agent's ProcessName.

- cmd/check.go: agent-aware process check (skips when sessErr != nil
  since the agent identity is unknown without a stored row).

- cmd/yolo.go / yolo_runners.go: drops fireServeEvent and
  proc.EnsureServeRunning callsites; resolveSimpleName falls back to
  session.DefaultAgent.

- cmd/new.go, cmd/kill.go: drop serve-event firing (kept fireHook so
  user-defined shell hooks still run).

- cmd/hooks_dispatch.go: keeps fireHook + yoloIntent; deletes
  fireServeEvent + the internal/serve/proc import.

- cmd/doctor.go + internal/doctor/doctor.go: deps list flips
  {tmux, claude, git} → {tmux, codex, git}. cmd/root.go's Short/Long
  rebrand to "Codex Tmux Manager".

- cmd/install.go: drops the cc-sessions migration step and the prose
  block describing the (now-removed) ~/.config/ctm/claude-overlay.json
  injection.

- integration_test.go: removes TestIntegration_MigrateFromCC since the
  cc migration is gone.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ude package

Removes every CLI surface that only existed to manage Claude Code state.
With the codex agent shipped (and the session layer now routing through
the registry), the claude implementation becomes inert: deleting it
trims ~40 files of code with no functional regression for codex users.

- cmd/overlay.go + test: claude-overlay.json mechanism (--settings layer
  on top of ~/.claude/settings.json). Codex reads ~/.codex/config.toml
  natively; ctm no longer maintains a parallel layer.

- cmd/statusline.go + test: parsed claude's statusLine JSON payload to
  render Opus/effort/context/quota. Codex doesn't expose equivalent
  telemetry — remove rather than ship a misleading half-impl.

- cmd/logs.go + 2 tests: viewer for claude PostToolUse JSONL logs.
  Source data is gone; viewer goes too.

- cmd/log_tool_use.go + test: PostToolUse hook target.

- internal/config/claude_env.go + test: claude-env.json shell-prelude
  loader (was the early-bind path for CLAUDE_CODE_NO_FLICKER, etc.).

- internal/health/claude_check.go + test: superseded by the
  agent-agnostic CheckAgentProcess landed in the codex commit.

- internal/shell/migrate.go + test: cc-sessions → ctm migration for
  the pre-claude-code "cc" CLI. With claude itself gone, this legacy
  path has no audience.

- internal/agent/claude/* (8 files): the Agent implementation we're
  replacing. registry_test.go's stubAgent already implements
  DiscoverSessionID so the registry tests still pass.

- cmd/bootstrap.go: drops ensureOverlaySidecars + pruneSessionLogs +
  the logrotate import. ensureSetup now does only config + tmux.conf
  + aliases.

- cmd/bootstrap_test.go: rewritten to assert the new minimal contract
  (config.json + tmux.conf created; no overlay/env sidecars leak).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…2 migration

ctm is now a pure CLI. The embedded React UI was already disconnected
from any new data path; this commit removes the HTTP daemon that fed
it, along with everything the daemon required:

- internal/serve/** (104 files): HTTP server, SSE event bus, API
  endpoints, ingest tailer / cost store / search store, attention
  thresholds, webhook dispatcher, V27 single-user auth.

- cmd/serve.go: the daemon entry-point.
- cmd/auth.go: `ctm auth reset` — only useful for daemon credentials.

- internal/config/config.go: drops ClaudeOverlayPath, ClaudeEnvPath,
  ServeConfig, AttentionThresholds, AllowedOriginsPath, and all serve
  port / attention default constants. Existing user config.json files
  with a "serve" block hit the jsonstrict strip-to-.bak self-heal
  path the first time the post-v0.3 binary loads them — documented
  unknown-key behaviour, no migration required.

- SchemaVersion bumped to 2 with rewriteRequiredPathClaude — a defensive
  migration that rewrites literal "claude" entries in required_in_path
  to "codex" so existing installs don't see a spurious doctor warning.
  Idempotent; preserves user additions beyond the original defaults.

- internal/config/config_test.go: drops the Serve/AttentionThresholds
  test bloc and the legacy hardcoded SchemaVersion==1 assertions;
  adds four migration tests (happy path, user additions preserved,
  idempotent, full migrate.Run plan) plus reflect+migrate imports.

- go.mod + go.sum: `go mod tidy` drops fsnotify, mattn/go-sqlite3,
  and golang.org/x/crypto (all daemon-only deps).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The React + Vite + Playwright dashboard at ui/ was the consumer of the
HTTP daemon removed in the previous commit. With no consumer left, the
entire frontend tree comes out (~150 files: src/, e2e/, public/, plus
package.json / pnpm-lock / tsconfig / vite / vitest / eslint / etc.).

- Makefile: rewritten to a CLI-only build pack. Drops UI_DIR / UI_DIST /
  EMBED_DIST variables, the `ui` / `dev` / `e2e` targets, and the
  pnpm/vite/playwright dependencies of `regression`. Drops the
  `sqlite_fts5` build tag (was for the deleted daemon's search store).
  `regression` now runs go build / go test / go test -race /
  govulncheck across the full module.

- .gitignore: drops `internal/serve/dist/` and `ui/coverage/` (paths
  that no longer exist); adds `.claude/*.local.md` so ralph-loop
  iteration snapshots and similar runtime state stay out of history.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…xtures

Cosmetic-only sweep across Go source where comments and test fixtures
still mentioned claude after the codex pivot. No behavior changes.

- internal/fsutil/atomic.go: package doc no longer references the
  deleted internal/claude/jsonpatch.go.
- internal/output/format_test.go: TestError + TestInfo fixtures
  swapped from "claude" to "codex" — agent-name strings are arbitrary
  here; matching the new default keeps future readers from inferring
  claude is supported.
- internal/tmux/client.go: SendEnter docstring's TUI example.
- internal/tmux/config.go: tmux.conf focus-events comment.
- internal/tmux/client_test.go: shellCmd fixtures rewritten to
  `codex resume <id> || codex` form (the function under test passes
  the string verbatim — codex/claude/anything works, but the new
  literal documents the current spawn shape).
- sonar-project.properties: comment references list codex as the
  expected `$PATH` binary; coverage-exclusion roster trimmed to files
  that still exist post-daemon removal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…TRIBUTING, .bestpractices.json; trim CI

User-facing surface flipped to the post-codex-pivot, post-daemon-removal,
CLI-only state.

- README.md: tagline "Claude Tmux Manager" → "Codex Tmux Manager".
  Drops the entire overlay/statusline/logs sections plus all daemon /
  web-UI / pnpm / vite / playwright references. Statusline mock
  collapses to `codex 0.130.0  ~/projects/ctm  ●` with a one-line
  caption explaining codex doesn't expose telemetry. Resume bullet
  rewritten to `codex resume <id> || codex` with `codex resume --last`
  callout; YOLO bullet to `codex --sandbox danger-full-access`.
  Codex CLI link points to https://github.com/openai/codex.

- CHANGELOG.md: new [0.3.0] 2026-05-14 BREAKING entry documenting the
  daemon + web-UI removal. New [Unreleased] entry covers the v2
  config-schema migration, the .bestpractices.json refresh (manual
  re-ingest still required), and the codex thread-UUID discovery
  fix. Pre-existing 0.2.0 + 0.1.0 history left untouched.

- SECURITY.md: drops bearer-token / argon2id / Origin allow-list /
  reverse-proxy paragraphs; "Security architecture quick reference"
  now states explicitly that ctm has no network listener post-v0.3.

- CONTRIBUTING.md: drops pnpm/vite/playwright/eslint guidance and
  the sqlite_fts5 build-tag mention; keeps Go-side workflow.

- .bestpractices.json: surgical refresh — description + justification
  swap, drops sqlite_fts5 + pnpm + non-existent CodeQL refs, flips
  the four argon2id-coupled crypto criteria to N/A, reframes
  dynamic-analysis around the post-spawn discovery goroutine.
  Maintainer still needs to click "Save (and continue) 🤖" on
  bestpractices.dev project 12716 to trigger re-ingestion.

- .github/workflows/ci.yml + release.yml + sonar.yml: drop
  pnpm/Node setup, `make ui`, UI typecheck, vitest coverage,
  playwright steps. Header comments trimmed.

- .github/workflows/sonar-bulk-accept.yml: drops every TypeScript
  rule bucket (S6759/S6819/S3358/S6571/S6754/S6479/S3735/S1874/
  S7763/S7718/S6772/S6582/S4624/S6822/S1871). Keeps Go-side buckets
  and the godre:S8239 false-positive bucket.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
51.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@aksOps aksOps merged commit 958348b into main May 15, 2026
10 of 12 checks passed
@aksOps aksOps deleted the worktree-codex-default branch May 15, 2026 07:05
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