Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/codex-host-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agent-bundle": minor
---

Validate built Codex bundles against vendored pinned schemas, report the host's missing plugin-validation developer tool honestly, and expose bounded app-server schema drift evidence through the public and test APIs.
14 changes: 14 additions & 0 deletions docs/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ gate a build, a validation, or a dev rebuild.
| `AB8xxx` | Development server configuration. |
| `AB9xxx` | Eval selection, harnesses, and persisted runs. |

## Codex host validation (`AB6030`–`AB6033`)

Codex 0.147.0 publishes plugin installation commands but no plugin-validation
developer tool. Agent Bundle therefore validates built Codex JSON documents
against its vendored pinned schemas and treats the app-server schema generator
as a separate drift signal, never as a substitute plugin contract.

| Code | Severity | Meaning | Recovery |
| --- | --- | --- | --- |
| `AB6030` | info | The Codex CLI is unavailable, or the installed Codex release publishes no plugin validation command. | Install Codex and put it on `PATH`; until Codex publishes a validator, use the vendored pinned-schema diagnostics. |
| `AB6031` | info / warning (error in strict mode) | The app-server schema-generation verb is unavailable, or its live output is missing or differs from the pinned generated hook schemas. | Review the attributable host schema source and update the pinned revision only when Codex publishes the matching contract. |
| `AB6032` | error | A required Codex bundle document is missing, unreadable, invalid JSON, or fails its vendored pinned schema. | Repair the named `.codex-plugin/plugin.json`, `hooks/hooks.json`, `.mcp.json`, or marketplace document and rebuild. |
| `AB6033` | error | A bounded Codex version or schema-generation command could not start, failed, timed out, exceeded 1 MiB of output, or produced unreadable output. | Verify `codex --version` and `codex app-server generate-json-schema --out <dir>` complete successfully, then rerun validation. |

## npm prepack gate (`AB7010`–`AB7013`)

| Code | Meaning |
Expand Down
51 changes: 51 additions & 0 deletions packages/agent-bundle/src/adapters/capabilities/codex-0.147.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,57 @@
"marketplace": ".agents/plugins/marketplace.json",
"skills": true
},
"validation": {
"observedAt": "2026-09-02",
"versionProbe": {
"command": "codex --version",
"exitCode": 0,
"stdout": "codex-cli 0.147.0"
},
"pluginDeveloperTools": {
"command": "codex plugin --help",
"exitCode": 0,
"subcommands": ["add", "list", "marketplace", "remove", "help"],
"validator": {
"reason": "Codex 0.147.0 lists plugin installation and marketplace management only; it publishes no plugin validate command.",
"state": "unavailable"
}
},
"appServerSchemaGeneration": {
"command": "codex app-server generate-json-schema --out <DIR>",
"exitCode": 0,
"jsonFileCount": 285,
"stderrBytes": 0,
"stdoutBytes": 0,
"topLevelBundles": {
"codex_app_server_protocol.schemas.json": {
"sha256": "f72b2caa3cbfa4298de9e85c62dda6dfbaf2266ffeb916fed30615ca69ff8c74"
},
"codex_app_server_protocol.v2.schemas.json": {
"sha256": "f3dec1e031d99a420b137b903f02196d4325eece57620c925bb7130b25f168d2"
}
}
},
"pinnedGeneratedComparison": {
"liveRevision": "codex-cli 0.147.0 app-server protocol",
"matchingRelativePaths": [],
"missingPinnedRelativePaths": [
"subagent-start.command.input.schema.json",
"subagent-start.command.output.schema.json",
"subagent-stop.command.input.schema.json",
"subagent-stop.command.output.schema.json"
],
"pinnedRepositorySha256": {
"subagent-start.command.input.schema.json": "e1cacc5cd92217e96e327cf182038fa93099d194c3107439b4dad4b806d414cc",
"subagent-start.command.output.schema.json": "531f7a457ad8430de82388319ff2bf030fd3a1dc0e9a0d4078447bc30948448b",
"subagent-stop.command.input.schema.json": "27842578768e74fb8bcd86b30156b207011829a81dc01b00cfd55340df8b079f",
"subagent-stop.command.output.schema.json": "a3987dab22b8684ab108bbb76ec5306471d9e01b1b7cac007b1ed90bc9e055cf"
},
"pinnedRevision": "rust-v0.147.0 hook command schemas",
"reason": "The command emits app-server protocol schemas, not the vendored hook command schemas. There are no matching relative paths to hash-compare, so this output is unpinned for plugin conformance.",
"state": "unavailable"
}
},
"tokens": {
"pluginData": false,
"pluginRoot": "relative-with-plugin-root-cwd",
Expand Down
26 changes: 19 additions & 7 deletions packages/agent-bundle/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ import {
validateClaudePlugin,
type ClaudePluginValidationReport,
} from './host-contracts/claude-plugin-validation.ts';
import {
validateCodexPlugin,
type CodexPluginValidationReport,
} from './host-contracts/codex-plugin-validation.ts';
import type { EvalComparison } from './eval/compare.ts';
import { EvalRunStoreError } from './eval/errors.ts';
import {
Expand Down Expand Up @@ -160,6 +164,8 @@ export type {
NativeHost,
RedactedEventEnvelope,
} from './host-contracts/host-contract.ts';
export { validateClaudePlugin, validateCodexPlugin };
export type { ClaudePluginValidationReport, CodexPluginValidationReport };

export { HookService } from './services/hook-service.ts';
export type { HookListOptions, HookSimulationOptions } from './services/hook-service.ts';
Expand Down Expand Up @@ -237,7 +243,7 @@ export interface ValidateOptions extends ProjectOptions {

export interface ValidateResult {
readonly diagnostics: readonly Diagnostic[];
readonly hostValidation?: readonly ClaudePluginValidationReport[];
readonly hostValidation?: readonly (ClaudePluginValidationReport | CodexPluginValidationReport)[];
readonly model?: NormalizedPlugin;
}

Expand Down Expand Up @@ -478,12 +484,18 @@ export const validate = async (options: ValidateOptions): Promise<ValidateResult
return Object.freeze({ diagnostics: freezeDiagnostics(validated.diagnostics) });
}
const reports = await Promise.all(validated.snapshot.manifest.targets
.filter((target) => target.name === 'claude' || target.name === 'plugin')
.map((target) => validateClaudePlugin({
pluginDirectory: join(artifact, target.name),
strict: options.strict,
target: target.name,
})));
.filter((target) => target.name === 'claude' || target.name === 'codex' || target.name === 'plugin')
.map((target) => target.name === 'codex'
? validateCodexPlugin({
pluginDirectory: join(artifact, target.name),
strict: options.strict,
target: target.name,
})
: validateClaudePlugin({
pluginDirectory: join(artifact, target.name),
strict: options.strict,
target: target.name,
})));
return Object.freeze({
diagnostics: freezeDiagnostics([
...validated.diagnostics,
Expand Down
Loading
Loading