Skip to content

cursor: BYOA slice 3 — Claude Code adapter (topology A, headless) - #508

Closed
servathadi wants to merge 3 commits into
mainfrom
cursor/task-b74063a2
Closed

cursor: BYOA slice 3 — Claude Code adapter (topology A, headless)#508
servathadi wants to merge 3 commits into
mainfrom
cursor/task-b74063a2

Conversation

@servathadi

Copy link
Copy Markdown
Contributor

Dispatched to the cursor agent (Grok) headless via the mupot loop for task b74063a2-82ca-4e5f-841f-b716730cc659.

Task done-when: A claude-code runtime adapter conforming to runtime-adapter/v1 dispatches via claude -p "<prompt>" headless (--output-format stream-json), reads mupot's remote MCP via .mcp.json (type:http, url, headers.Authorization), same land-at-review contract; conformance smoke green; starts from the existing packs/claude-code/flock-agent pack; PR opened (NOT merged).

Driver verified: cursor committed real work + tsc --noEmit clean. Kasra-core gates this PR before merge (the task is in review; cursor cannot self-close it).

kasra and others added 3 commits July 23, 2026 15:01
Make the topology-A workers the reference adapter: shared client with
server-derived identity, declared runtime types, fleet-attach:v1, and
land-at-review rails — so later BYOA harnesses copy one contract, not
bespoke glue.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add topology-A codex-worker that dispatches via `codex exec --sandbox --json`,
reads mupot MCP over streamable-HTTP (url + bearer_token_env_var, no SSE),
filters own-assignee open tasks, isolates a worktree, verifies, and lands at
review — proving the contract generalizes past cursor for cursor→codex failover.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add topology-A claude-code-worker that dispatches via `claude -p` with
`--output-format stream-json`, writes mupot remote MCP as type:http
`.mcp.json` (url + headers.Authorization) from the flock-agent pack, and
lands at review — completing confirmed headless CLI breadth with cursor+codex.

Co-authored-by: Cursor <cursoragent@cursor.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 922f5cc6f2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

git("worktree", "add", "-b", branch, str(worktree), "main")
try:
ensure_worktree_mcp_json(worktree, cfg.mcp_url, cfg.token, write=True)
proc = claude_run(worktree, build_brief(task, worktree, branch))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Mark timed-out Claude tasks as blocked

When claude exceeds the configured TIMEOUT, subprocess.run raises TimeoutExpired here before verification or report_blocked runs. The finally block removes the worktree, the outer loop only logs the exception, and future cycles poll only open tasks, so the already-claimed in_progress task is stranded indefinitely instead of being surfaced as blocked.

Useful? React with 👍 / 👎.

Comment thread scripts/codex-worker.py
git("worktree", "add", "-b", branch, str(worktree), "main")
try:
ensure_codex_mcp_config(cfg.mcp_url, CODEX_MCP_ENV_VAR, write=True)
proc = codex_run(worktree, build_brief(task, worktree, branch), cfg.token)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Mark timed-out Codex tasks as blocked

When codex exec exceeds TIMEOUT, this call raises TimeoutExpired; control skips the verification failure path, removes the worktree in finally, and is merely logged by the outer loop. Because the task was already changed to in_progress and subsequent polling requests only open tasks, that task will never be retried without manual intervention.

Useful? React with 👍 / 👎.

Comment thread scripts/codex-worker.py
Comment on lines +154 to +156
if prior and f'bearer_token_env_var = "{env_var}"' in prior.group(0) and f'url =' in prior.group(0):
log(f"mcp config already present in {CODEX_CONFIG}")
return stanza

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Replace a Codex MCP stanza when its URL differs

If ~/.codex/config.toml already contains a syntactically valid mupot stanza for another pot, this condition treats it as current without comparing its URL to mcp_url. The driver then authenticates its own calls against the newly configured pot while codex exec connects to the stale endpoint, commonly with a token minted for a different pot, so MCP context is unavailable or comes from the wrong deployment.

Useful? React with 👍 / 👎.

Comment on lines +163 to +164
if ignore_line not in existing.splitlines():
gitignore.write_text(existing.rstrip() + "\n" + ignore_line + "\n")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the generated MCP ignore rule out of task commits

When the repository does not already ignore .mcp.json (as is currently the case), setup modifies the tracked .gitignore inside the task worktree. An agent following the usual git add -A workflow will commit this driver-created change with its work, and an agent that otherwise produced no work can commit only this line and satisfy the driver's commit-plus-tsc verification, resulting in a false-green PR.

Useful? React with 👍 / 👎.

"//": "Flock agent pack — Claude Code. Connects this agent to a mupot flock over the SOS bus.",
"//token": "Replace <SCOPED_BUS_TOKEN> with the project-scoped, agent-bound token your operator minted (project=<slug>, agent=<your-agent-name>). NEVER commit this file with a real token — it is gitignored.",
"//": "Flock agent pack — Claude Code (BYOA topology A). Connects this agent to mupot over remote HTTP MCP.",
"//token": "Replace <MUPOT_MEMBER_TOKEN> with the project-scoped, agent-bound token your operator minted. NEVER commit this file with a real token — it is gitignored.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not claim the member token is project-scoped

The prescribed mint_agent_token credential is not project-scoped: mintAgentBoundToken grants member on the agent's entire squad, and task_list defaults to that squad unless a project_id is explicitly supplied. When a squad contains multiple projects, an agent installed from this pack can therefore read and mutate tasks outside its purported project boundary, despite the template and skill telling operators that the token enforces project isolation.

Useful? React with 👍 / 👎.

OUTPUT_FORMAT,
]
if SKIP_PERMISSIONS:
cmd.append("--dangerously-skip-permissions")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Isolate Claude from the driver's Git credentials

On the production host where the driver can push and open PRs, this default --dangerously-skip-permissions Claude process inherits the same environment, repository remote, credential helpers, and SSH agent because subprocess.run is invoked without a sanitized environment or network sandbox. Claude therefore has unrestricted Bash access to git push/gh and can bypass the stated human-gated delivery boundary; the prompt saying not to push is not an enforcement mechanism.

Useful? React with 👍 / 👎.

Comment on lines +205 to +207
def _load_ed25519_private_key(key_path: Path) -> Any:
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Declare the dependency required for signed attach

When an operator sets any advertised *_KEY option on a normal Python installation, the signed-attach path imports the third-party cryptography package, but the repository supplies no Python requirements or installation step for it. On such hosts the import fails, boot_session silently treats attach as non-fatal, and bearer fallback cannot work for a registered-key agent because the server intentionally rejects that downgrade, leaving the advertised signed runtime unattached.

Useful? React with 👍 / 👎.

Comment on lines +212 to +217
commits = git("log", "main..HEAD", "--oneline", cwd=worktree, check=False).stdout.strip()
if not commits:
return False, "no commits — claude-code produced no work"
tsc = subprocess.run(["npx", "tsc", "--noEmit"], cwd=str(worktree), capture_output=True, text=True)
if tsc.returncode != 0:
return False, f"tsc errors:\n{tsc.stdout[-1500:]}{tsc.stderr[-500:]}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Verify the committed Claude tree instead of the dirty worktree

If Claude creates at least one commit and then leaves later edits unstaged or uncommitted, the commit check succeeds while tsc validates the dirty working tree containing those later edits. Delivery pushes only the commits and the forced worktree removal discards the remaining changes, so the opened PR can omit code required for the successful verification and can be incomplete or uncompilable.

Useful? React with 👍 / 👎.

Comment thread scripts/codex-worker.py
Comment on lines +214 to +219
commits = git("log", "main..HEAD", "--oneline", cwd=worktree, check=False).stdout.strip()
if not commits:
return False, "no commits — codex produced no work"
tsc = subprocess.run(["npx", "tsc", "--noEmit"], cwd=str(worktree), capture_output=True, text=True)
if tsc.returncode != 0:
return False, f"tsc errors:\n{tsc.stdout[-1500:]}{tsc.stderr[-500:]}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Verify the committed Codex tree instead of the dirty worktree

If Codex commits an initial change but leaves subsequent edits uncommitted, this accepts the existing commit and runs tsc against the dirty worktree rather than the exact tree that will be pushed. The driver then pushes only committed content and force-removes the worktree, so a PR may lack the edits that made verification pass.

Useful? React with 👍 / 👎.

Comment on lines +430 to +439
try:
ack = attach(cfg, identity, host=host)
agent_view = ack.get("agent") if isinstance(ack.get("agent"), dict) else {}
log(
f"attach ok runtime={agent_view.get('runtime', cfg.runtime)!r} "
f"status={agent_view.get('status', 'running')!r}"
)
except Exception as exc: # noqa: BLE001 - attach failure must not block task work
log(f"attach failed (non-fatal this cycle): {exc}")
register_port1_presence(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep dry-run boot sessions read-only

The drivers invoke boot_session even in their documented DRY_RUN=1 paths, and this unconditional call performs both a fleet attach and presence_register. A dry run with a valid token therefore mutates production presence state and marks the runtime running despite promising to poll and print without doing anything; identity-only dry runs need to avoid the attach and registration writes.

Useful? React with 👍 / 👎.

@servathadi servathadi left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

BLOCK — exact-head content gate at 922f5cc6.

The Claude worker launches with --dangerously-skip-permissions and inherits the driver's environment, repository credentials, credential helpers, SSH agent, and unrestricted network access. The prompt's “do not push” instruction is not an isolation boundary, so the builder can bypass the human-gated delivery path. Both Claude and Codex verification also tests the dirty worktree rather than the committed tree that will be pushed, allowing a false-green PR.

These are P1 authority and evidence-integrity blockers. Existing exact-head threads remain unresolved; the branch is merge-conflicted and has CodeQL-only evidence rather than the required full gate suite.

@servathadi

Copy link
Copy Markdown
Contributor Author

Tracked as #559 for incremental fix (2026-07-25 backlog triage — codex diverse-gate BLOCK, real finding, not merged/live). Leaving this PR open as the fix branch; will re-request gate and merge on GREEN when picked up.

@servathadi

Copy link
Copy Markdown
Contributor Author

Closing — investigated per squad ceremony (kasra-code, 2026-07-29). Evidence:

  1. Real unresolved P1/P0 security findings, never fixed. The 2026-07-23 codex diverse-gate left 11 open review threads on this PR — including the exact pair tracked in [BLOCK] PR #508: Claude inherits push creds + dirty-tree verification #559 ("Claude inherits push creds + dirty-tree verification"): scripts/claude-code-worker.py runs claude --dangerously-skip-permissions via bare subprocess.run with no sanitized env/credential isolation (inherits the driver's git push creds + SSH agent), and verifies tsc against the dirty worktree rather than the committed tree that actually gets pushed. Also unresolved: the .mcp.json.template mint claims project-scoped isolation that mintAgentBoundToken/task_list don't actually enforce (squad-wide), the bundled heartbeat.sh posts to the wrong endpoint/payload shape for a pot-native member token, and DRY_RUN=1 still performs a live fleet attach + presence write. None of these were addressed in the 3 commits on this branch after the 2026-07-23 review.

  2. Entangled with the dead slice-2 lineage. This PR's diff isn't scoped to slice 3 — it also adds scripts/codex-worker.py (450 lines) and the shared scripts/runtime_adapter_v1.py, i.e. it carries slice 2 (Codex CLI adapter, cursor: BYOA slice 2 — Codex CLI adapter (topology A, headless) #506) forward. cursor: BYOA slice 2 — Codex CLI adapter (topology A, headless) #506 was closed unmerged 2026-07-25 (dead — confirmed today via gh pr view 506 --json state,mergedAt: state=CLOSED, mergedAt=null). Its dedicated fix-PR, cursor: Fix PR #506 (Codex CLI adapter / runtime-adapter de-drift) — #522 (which actually addressed cursor: BYOA slice 2 — Codex CLI adapter (topology A, headless) #506's P0s: fail-closed attach, server-derived capabilities, minimal env allowlist), was just closed today as dead weight since the target never landed. Merging cursor: BYOA slice 3 — Claude Code adapter (topology A, headless) #508 as-is would resurrect that same unfixed codex-worker.py under a different PR number.

  3. Not superseded by anything already on main. Confirmed via git ls-tree -r origin/main — no claude-code-worker.py, codex-worker.py, or runtime_adapter_v1.py exists on main; the design doc (docs/superpowers/specs/2026-07-23-byoa-harness-support-matrix-design.md) still lists slice 3 as not built. So this isn't dead because the work landed elsewhere — it's dead because it was never fixed and it's welded to a slice that was abandoned.

  4. Mechanically conflicting, not live/merged, no production exposure.

If slice 3 (Claude Code adapter, topology A) is still wanted, the epic in docs/superpowers/specs/2026-07-23-byoa-harness-support-matrix-design.md is unchanged and worth revisiting — but as a fresh, narrowly-scoped branch (claude-code-worker.py + connectors/claude + packs/claude-code only, not re-carrying codex-worker.py), built to actually close the P1s above from the start: sanitized subprocess env for the CLI child, verify the committed tree not the worktree, correct token-scope claims, correct heartbeat endpoint, and a genuinely read-only DRY_RUN path.

— kasra-code

@servathadi servathadi closed this Jul 29, 2026
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