Skip to content

Run Codex discovery as a trusted command hook - #153

Merged
sudoHG merged 3 commits into
mainfrom
sudoHG/152-codex-command-hook
Oct 5, 2026
Merged

sudoHG merged 3 commits into
mainfrom
sudoHG/152-codex-command-hook

Conversation

@sudoHG

@sudoHG sudoHG commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Closes #152

Summary

  • Run Codex discovery through askkey hook codex with bounded EOF JSON, explicit session/turn isolation, and the shared SSH reminder; successful catalog callbacks settle the reminder, while failures or missing callbacks release after 30 seconds without progress.
  • Replace only an exact legacy MCP-tool group, preserve unrelated hooks/order/mode, and retain the existing backups, concurrent-edit detection, and readback protections.
  • Enable and trust both exact command definitions only during explicit Connect, using native current hashes and actual keys; preserve state outside the two current keys, treating reused legacy keys as requiring fresh trust.
  • Require both handlers for onboarding readiness and reject helpers without Codex command capability; MCP no longer requires the legacy guard tool, whose behavior remains unchanged.

Diff stat

git diff --stat origin/main: 18 files changed, 815 insertions(+), 292 deletions(-).

Head: 3c17325351c44afc160be405a5c1537dbd5c26b7 (pushed and confirmed by git ls-remote). Base: e24d953.

Tests

Local commands, with no top-level ASKKEY_* overrides:

swift build
swift test --filter 'CodexCommandDiscoveryHookTests|CodexDiscoveryHookConfigurationTests|CodexNativeHookClientTests|CodexCommandHelperVerificationTests|CodexOnboardingSetupTests|CommandDiscoveryHookTests|ClaudeDiscoveryHookTests|ApprovalStateMachineDecisionTests|ApprovalStateMachineAllowanceTests'
swift test --filter 'CodexNativeHookClientTests|CodexCommandHelperVerificationTests|HelperMCPTests'
swift test --filter 'CodexNativeHookClientTests|CodexOnboardingSetupTests'
swift test --filter CodexCommandDiscoveryHookTests
swift test --filter 'CodexCommandDiscoveryHookTests|CommandDiscoveryHookTests|ClaudeDiscoveryHookTests'
  • Build passed. Targeted runs passed 76/76, 25/25, 25/25, and 6/6 respectively; the final migration follow-up repeated swift build and swift test --filter 'CodexNativeHookClientTests|CodexOnboardingSetupTests' (26/26, repeated after extracting the fixture to meet the 600-line limit). The final no-progress refinement repeated build plus all three command-hook suites (23/23): unmatched Codex Post callbacks cannot refresh the deadline; other clients retain their behavior. All runs had zero failures and zero skips. These runs cover 86 distinct final tests, including unchanged Claude/Cursor/Grok behavior, approval decisions/allowances, and the retained legacy MCP guard.
  • Final suite counts: Codex command behavior 6; configuration 21; native verification/trust 19; helper capability/MCP verification 3; onboarding 7; existing command adapters 8; Claude 9; approval decisions 5; approval allowances 4; legacy MCP 4.
  • Compared with origin/main test definitions: 20 added tests; existing counts retained. Main was not rebuilt/retested locally. The full suite and desktop flows are reserved for PR CI.
  • Coverage includes exact legacy migration/idempotence, unrelated groups before/after both events, mode/backup retention, customized/duplicate/partial definitions, disabled/untrusted handlers, unsupported helper capabilities, MCP without the legacy guard, malformed metadata, concurrent edits, catalog-before-SSH sequencing, sessions/turns/late callbacks, no-progress release without false completion, malformed/oversized input, empty allowed output, and no execution of tool input.

Checks

  • python3 scripts/check_hygiene.py: passed (size=0, test-support=0, debug=0, local-path=0, non-ascii-name=0, multica=0, cjk=0).
  • python3 scripts/check_module_deps.py: passed.
  • git diff --check: passed.
  • Acquired a build slot with mkdir /tmp/askkey-build-slot-1 before local builds/tests; no local full-suite run, app launch, screenshots, UI automation, or run-e2e.sh.
  • Latest-head CI run 37258688708 passed for 3c17325351c44afc160be405a5c1537dbd5c26b7: build-and-test green (8m49s); basic-ui-flows green (8m31s). All workflow steps passed, including required desktop flows, optional screenshots, release-symbol checks, optimized cancellation runtime, automation checks, development launcher, hygiene, and module dependencies.
  • CI full suite: 1,020 total tests, 1,017 passed, 3 skipped, 0 failed. Skips are the opt-in installed-Codex CLI contract, isolated abrupt file-commit subprocess entry, and explicit synthetic native-bootstrap fixture generator. The standalone Codex command-hook contract/trust/migration checks above ran separately and passed.
  • CI reported 15 unique compiler warning locations/messages, all in unmodified files (Sendable conversions/captures, deprecated Keychain APIs, a redundant modifier, and weak-variable declarations), plus Node deprecation notices from workflow tooling. No compiler warning points to this PR's changed files.
  • Cleanup: removed both isolated Codex homes/source/probes and full local logs (about 20 MiB); released the local build slot. Data volume has 227 GiB free. Shared xcodebuild, swift-frontend, testmanagerd, and launchd_sim processes remain active outside this task, so the task's .build (about 1 GiB) is retained under the disk-hygiene rule until those processes are idle or the coordinator removes this worktree after merge. No task build/test process remains; today's XCTest device directories are preserved.

Codex 0.160.0 contract and isolated real process check

Coordinator approved the contract before parser/configuration edits. Primary source: Codex tag rust-v0.160.0, peeled commit a956835d020762cb2b570053af06f643a11c0ecc.

Source checks: codex-rs/hooks/schema/generated/{pre,post}-tool-use.command.input.schema.json, hooks/src/events/pre_tool_use.rs, hooks/src/engine/command_runner.rs, core/src/tools/{hook_names,registry}.rs, and core/src/tools/handlers/mcp.rs. They establish UserPromptSubmit, PreToolUse, and PostToolUse, explicit session_id/turn_id/tool_use_id, shell identity Bash with tool_input.command, catalog identity mcp__askkey__list_credentials, and the exit-0 denial envelope hookSpecificOutput.{hookEventName:PreToolUse,permissionDecision:deny,permissionDecisionReason:...}. Empty stdout allows; timeout:3 covers command stdin/output execution and times out as a non-blocking failure. There is no PostToolUseFailure; the tool registry emits Post callbacks only for successful tool results, so failed/missing callbacks use the 30-second no-progress release.

The real-process check used a freshly created temporary home and a minimal environment, with no model turns, authentication, MCP startup, or installed helper execution. Commands (temporary paths represented by variables):

contract=$(mktemp -d /tmp/askkey-152-contract.XXXXXX)
git clone --depth 1 --branch rust-v0.160.0 --filter=blob:none --sparse https://github.com/openai/codex.git "$contract/source"
git -C "$contract/source" sparse-checkout set codex-rs/hooks codex-rs/core/src/tools codex-rs/protocol/src
git -C "$contract/source" rev-parse HEAD
codex_bin=$(command -v codex)
env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin HOME="$contract/home" CODEX_HOME="$contract/home/.codex" "$codex_bin" --version
env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin HOME="$contract/home" CODEX_HOME="$contract/home/.codex" TMPDIR="$contract" "$codex_bin" app-server --disable plugins --disable apps

Created only temporary config.toml ([features] hooks = true) and hooks.json containing one group in each of PreToolUse and PostToolUse, with matcher ^(Bash|mcp__askkey__list_credentials)$ and handler {type:command,command:"\"/Applications/Ask Key.app/Contents/Helpers/askkey\" hook codex",timeout:3}. The helper path was definition text only.

JSONL RPC sequence: initialize with experimentalApi:true; initialized; hooks/list with the temporary home as cwds; config/read with includeLayers:true; config/batchWrite against the temporary user file with its returned expectedVersion, reloadUserConfig:true, and an upsert of hooks.state for exactly the two returned keys (enabled:true, trusted_hash:<currentHash>); hooks/list readback. Then terminate and start a fresh app-server twice, repeating initialize/list without writing trust again.

Observed output:

codex-cli 0.160.0
source HEAD: a956835d020762cb2b570053af06f643a11c0ecc
initial hooks/list: 2 command handlers; source=user; isManaged=false; async=false; enabled=true; timeoutSec=3; errors=[]
Pre key: $CODEX_HOME/hooks.json:pre_tool_use:0:0
Post key: $CODEX_HOME/hooks.json:post_tool_use:0:0
config/batchWrite: status=ok
immediate readback: both trusted, enabled=true, errors=[]
fresh start 1: both trusted, enabled=true, same hashes, errors=[]
fresh start 2: both trusted, enabled=true, same hashes, errors=[]
Event Current hash (unchanged across both restarts)
PreToolUse sha256:36a292facf0ac00779326135032dc4bcaa83fde4580b1b8822b8dafb18322a45
PostToolUse sha256:bb0257288466d136e4d680816417367e69cfbf3cb36730c842059f3352ae5b21

The empty-file index is only an observation: implementation selects handlers by exact event/matcher/type/command/timeout metadata and uses their returned keys/hashes. Tests put unrelated groups before and after ours and preserve their disabled state plus stale legacy state outside the two current keys.

A second isolated check started with the exact legacy MCP group, trusted it through the same app-server RPCs, terminated the process, replaced the group in place with both command definitions, and started a fresh app-server. It confirmed that the legacy and new Pre handler reuse the same positional key, while the new definition is reported as modified (not trusted). Explicitly trusting the new current hashes succeeded, and two further fresh starts both remained trusted with those same new hashes:

legacy key: $CODEX_HOME/hooks.json:pre_tool_use:0:0
legacy hash: sha256:de649513d3d2d2d50c5a9747079e3fa5879d89ea2e9fb15238e7923b0efa6c93
migrated Pre key: same as legacy key; handlerType=command; trustStatus=modified
new Pre/Post hashes: 36a292facf0ac00779326135032dc4bcaa83fde4580b1b8822b8dafb18322a45 / bb0257288466d136e4d680816417367e69cfbf3cb36730c842059f3352ae5b21 (sha256)
config/batchWrite: status=ok; both new definitions trusted
fresh start 1: both trusted, enabled=true, same new hashes, errors=[]
fresh start 2: both trusted, enabled=true, same new hashes, errors=[]

The coordinator explicitly approved the key-reuse interpretation: do not reuse the old hash; update only the two current command keys, including a reused legacy key, during explicit Connect. Every other entry stays untouched. Regression coverage verifies modified is reconnect/untrusted before Connect, writes the new hash to the reused key, and completes onboarding after fresh trust.

Deviations and questions

None in implementation scope; the coordinator-approved clarification for reused native trust keys is recorded above. Real repeated Orca-session trust/discovery acceptance remains the maintainer's installed-release task in #127; source/unit/standalone app-server checks do not establish that outcome. No production vault, real credentials, real Codex home, Orca runtime home, or installed app was read or modified.

@luoji-bot

luoji-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown

Review: approved for merge pending the maintainer. Checked the helper adapter (bounded EOF input, session/turn keying, unmatched Codex callbacks do not refresh the deadline), exact legacy migration in place with Post appended, position-independent native verification and trust of exactly the two current keys, readiness for legacy-only and partial definitions, and the docs. The isolated Codex 0.160.0 contract and restart evidence matches ADR 0009. Real Orca acceptance remains in #127.

@sudoHG
sudoHG merged commit 5122f7e into main Oct 5, 2026
2 checks passed
@sudoHG
sudoHG deleted the sudoHG/152-codex-command-hook branch October 5, 2026 03:33
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.

Run the Codex discovery hook as a command hook

1 participant