Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(config): generate config.schema.json from the zod schema - #536

Merged
ndycode merged 4 commits into
mainfrom
claude/audit-16-schema-generation
Jun 10, 2026
Merged

ndycode merged 4 commits into
mainfrom
claude/audit-16-schema-generation

Conversation

@ndycode

@ndycode ndycode commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Makes config/schema/config.schema.json generated from the zod PluginConfigSchema with a drift-guard test, so the JSON schema can never silently fall behind the real config surface again — audit roadmap §4.5.2 (docs/audits/AUDIT_2026-06-10.md, PR #522; the audit's finding M-class "schema drift" item).

Changes

  • lib/config-schema.ts (90 lines): builds and deterministically serializes the schema document using zod v4's native z.toJSONSchema()zero new dependencies. Preserves the existing $schema (draft 2020-12), $id, title, template root keys, and required: ["plugin","provider"] (the shipped config/*.json templates depend on them). Generated with io: "input" to match runtime semantics (loadPluginConfig strips unknown keys).
  • scripts/generate-config-schema.mjs + npm run generate:schema (build, then write from compiled dist).
  • test/config-schema-generated.test.ts (4 tests): regenerates in-memory and deep-equals the committed file — drift is now a test failure with the message "run npm run generate:schema". Build-independent (imports the TS module via vitest).
  • Regenerated config/schema/config.schema.json: from 3 stale root properties to the full 54-field pluginConfig definition in $defs (the audit's "75+" was an overestimate; 54 is the real PluginConfigSchema count). Every construct converted cleanly — no refinement workarounds needed.

Consumers verified

config/codex-modern.json / codex-legacy.json / minimal-codex.json reference the schema via $schema; test/config-schema-templates.test.ts validates them against it and still passes unchanged. The schema ships via the config/ entry in package.json files.

Validation

  • npm run typecheck; full npm run lint; eslint on touched files --max-warnings=0
  • Drift-guard + template + schema + documentation suites pass; generator run twice → byte-identical output
  • Full suite: 4,371 passed; the 58 failures are the documented environment-only set (verified identical on a pristine base checkout)
  • Independently re-verified: generated + template suites, 7/7

Risk / Rollback

Additive (new module/script/test) + a more complete schema file. Revert the single commit to restore the handwritten schema.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB


Generated by Claude Code

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

this pr wires config/schema/config.schema.json to the zod PluginConfigSchema so the shipped json schema can never silently fall behind the real config surface — previously it was handwritten and covered only 3 root properties. all previous review comments (crlf pinning via .gitattributes, ??= description guard, beforeAll file-read) are addressed in this iteration.

  • lib/config-schema.ts adds buildConfigJsonSchema() / renderConfigJsonSchema(), building the 54-field schema from z.toJSONSchema() with io:"input" to match loadPluginConfig semantics; deliberately excluded from lib/index.ts to keep it a generator-only module.
  • test/config-schema-generated.test.ts adds a drift guard: reads the committed file in beforeAll (with \r\n\n normalisation), then checks structural deep-equality, byte-for-byte serialisation identity, and field-set completeness against PluginConfigSchema.shape.

Confidence Score: 5/5

purely additive change — new module, script, test, and a more complete generated json schema; no existing runtime paths are modified

all previous review comments (crlf, beforeAll, description ??=) are addressed. the generator correctly uses io:"input" to match loadPluginConfig semantics, the drift guard test exercises both deep-equality and byte-level identity against the committed file produced by a separate process, and the gitattributes pin prevents windows autocrlf from producing spurious test failures. no logic that touches accounts, tokens, or the rotation proxy is changed.

no files require special attention

Important Files Changed

Filename Overview
lib/config-schema.ts new generator module — clean guard against non-object zod output, correct ??= for description, delete pluginConfig.$schema removes dialect duplication; intentionally not re-exported from lib/index.ts
test/config-schema-generated.test.ts drift-guard test suite; previous issues resolved — readFileSync moved into beforeAll, CRLF normalisation added, tautological same-process determinism test replaced with committed-file comparison
scripts/generate-config-schema.mjs minimal ESM generator script, imports from dist after build, rethrows import errors with a clear remediation message, uses import.meta.url-relative URL for the output path
.gitattributes adds config/schema/config.schema.json text eol=lf to pin the byte-compared schema file against Windows autocrlf checkout drift
config/schema/config.schema.json regenerated from PluginConfigSchema — expanded from 3 root stubs to the full 54-field $defs/pluginConfig structure, draft-2020-12 dialect, additionalProperties:true preserved
package.json adds generate:schema script (build then run generator); no dependency changes

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["lib/schemas.ts\nPluginConfigSchema (zod)"] -->|"z.toJSONSchema()\nio: input"| B["lib/config-schema.ts\nbuildConfigJsonSchema()"]
    B --> C["renderConfigJsonSchema()\nJSON.stringify + trailing \\n"]
    C -->|"npm run generate:schema\n(build then node script)"| D["scripts/generate-config-schema.mjs\nwrites to disk"]
    D --> E["config/schema/config.schema.json\n(committed, eol=lf)"]
    C -->|"vitest - test/config-schema-generated.test.ts"| F{"byte == committedRaw?\ndeep == buildConfigJsonSchema()?\nkeys == PluginConfigSchema.shape?"}
    E -->|"readFileSync in beforeAll\n+ CRLF normalise"| F
    F -->|"fail"| G["drift: run npm run generate:schema"]
    F -->|"pass"| H["schema in sync"]
Loading

Reviews (3): Last reviewed commit: "fix(config-schema): guard the zod output..." | Re-trigger Greptile

config/schema/config.schema.json was a handwritten 3-field template
schema (plugin/provider/model) while the real runtime config surface,
PluginConfigSchema in lib/schemas.ts, has 54 fields - so the JSON
schema had silently drifted from the config it claims to describe
(audit roadmap §4.5.2).

Generator approach: zod is already v4 (4.4.3), which ships native
z.toJSONSchema(), so no new dependency is needed. The schema document
is built in a new TypeScript module, lib/config-schema.ts, so both the
CLI generator and the test share one source:

- scripts/generate-config-schema.mjs imports the compiled
  dist/lib/config-schema.js (lib/ is TS and tsx/strip-types are not
  available, so `npm run generate:schema` builds first, then writes
  the file).
- test/config-schema-generated.test.ts imports the TS module directly
  through vitest, regenerates the schema in-memory, and asserts both
  deep-equality and byte-for-byte equality with the committed file, so
  any drift between PluginConfigSchema and the committed schema is a
  test failure with the fix spelled out: run `npm run generate:schema`.

Schema shape: root metadata ($schema draft 2020-12, $id, title) and
the template root keys are preserved because the shipped config/*.json
templates reference this file via $schema and
test/config-schema-templates.test.ts validates them against it. The
full PluginConfigSchema (all 54 fields, up from 3 total properties
before) is embedded as $defs.pluginConfig and referenced from an
optional root pluginConfig property, generated with io:"input" so the
definition matches runtime semantics (loadPluginConfig strips unknown
keys rather than rejecting them, so no additionalProperties:false).

Conversion caveats: none - every construct in PluginConfigSchema
(boolean/enum/bounded number/record of string arrays) converts cleanly;
no refinements needed describe()-style annotation. Output is
deterministic (zod emits shape-definition key order; 2-space indent and
trailing newline match the previous file).

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

this pr adds a config schema generator: a TypeScript module that builds deterministic JSON schemas from PluginConfigSchema via Zod, a CLI script that invokes the generator on demand, and a Vitest drift guard that byte-compares generated output to the committed schema to prevent accidental mutations.

Changes

Config Schema Generation System

Layer / File(s) Summary
Schema Generator Implementation
lib/config-schema.ts
Exports CONFIG_SCHEMA_RELATIVE_PATH constant and two functions: buildConfigJsonSchema() generates JSON schema by transforming PluginConfigSchema via z.toJSONSchema() and embedding it under $defs.pluginConfig with custom root metadata, and renderConfigJsonSchema() serializes the schema object deterministically (2-space indent, trailing newline) to ensure byte-identical output across process boundaries.
Generated Schema Document
config/schema/config.schema.json
Schema file expanded from minimal stub to full generated artifact with top-level type object, properties (plugin, provider, model, pluginConfig via $ref), required constraints, and comprehensive $defs.pluginConfig definition enumerating all typed runtime configuration fields including nested unsupportedCodexFallbackChain object structure.
Generation Command, Drift Guard, and Line Endings
.gitattributes, package.json, scripts/generate-config-schema.mjs, test/config-schema-generated.test.ts
.gitattributes enforces LF line endings on config.schema.json to prevent CRLF drift. package.json adds generate:schema script (npm run build && node scripts/generate-config-schema.mjs). CLI script dynamically imports renderConfigJsonSchema() from dist output and writes to config/schema/config.schema.json with UTF-8 encoding and fail-fast error messaging. test/config-schema-generated.test.ts (beforeAll) reads committed schema normalizing CRLF, then (1) deep-equals built schema, (2) byte-compares rendered string for determinism, and (3) validates metadata ($schema, $id, title) and that all PluginConfigSchema.shape keys appear in $defs.pluginConfig.properties.

Sequence Diagram

sequenceDiagram
  participant PluginConfigSchema as PluginConfigSchema<br/>(source)
  participant Generator as buildConfigJsonSchema()
  participant Renderer as renderConfigJsonSchema()
  participant File as config.schema.json
  participant Test as Drift Guard Test

  Generator->>PluginConfigSchema: import & traverse
  Generator->>Generator: z.toJSONSchema() + wrap
  Renderer->>Generator: read generated object
  Renderer->>Renderer: JSON.stringify(indent=2)
  Renderer->>File: write UTF-8 + LF
  Test->>File: read & normalize CRLF
  Test->>Generator: call buildConfigJsonSchema()
  Test->>Renderer: call renderConfigJsonSchema()
  Test->>Test: deepEqual & byte-compare
  Test->>Test: verify metadata & keys
Loading

Flags & Notes

windows edge case: gitattributes enforces LF (text eol=lf), and the test test/config-schema-generated.test.ts:1–79 explicitly normalizes CRLF to LF before comparison. this prevents spurious drift on windows checkouts, but verify that scripts/generate-config-schema.mjs:1–31 actually writes LF cleanly; if the build system or node version mangles line endings during the dynamic import, tests will fail on windows CI.

regression test coverage: the drift guard test at test/config-schema-generated.test.ts:1–79 is solid—it byte-compares rendered output and validates schema key coverage. good. but consider: does anything actually call the generate:schema script in CI? if it's purely on-demand, you risk stale schema in tree if a dev forgets to regenerate after adding new PluginConfigSchema fields. strongly recommend adding a pre-commit hook or CI check that fails if config.schema.json is out of date.

concurrency risk: none apparent. the generator is pure (reads PluginConfigSchema, produces JSON), the CLI writes atomically to one file, and tests run in isolation.


🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commit format (feat type, lowercase summary, 61 chars, imperative mood) and accurately describes the main change: generating config.schema.json from zod PluginConfigSchema.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR description is comprehensive with clear summary, detailed change breakdown, validation checklist completion, and risk assessment.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/audit-16-schema-generation
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/audit-16-schema-generation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread test/config-schema-generated.test.ts Outdated
Comment thread test/config-schema-generated.test.ts Outdated
Comment thread lib/config-schema.ts
- description ??= so a future .describe() on PluginConfigSchema wins
  over the generated default instead of being silently clobbered
- read the committed schema inside beforeAll so a missing file surfaces
  as a named failure with remediation, not an ENOENT collection crash
- drop the tautological in-process determinism test; the byte-for-byte
  comparison against the committed file (rendered by a separate
  generator process) is the real cross-invocation determinism check

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Comment thread test/config-schema-generated.test.ts
Review follow-up (P1): pin the generated schema file to LF via
.gitattributes (scoped to the one byte-compared file to avoid repo-wide
JSON churn on existing checkouts) and normalize CRLF when reading, so
autocrlf checkouts cannot produce spurious drift failures.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/config-schema.ts`:
- Around line 49-50: The long assignment to pluginConfig.description should be
split for readability: modify the assignment to build the string across multiple
parts (e.g., a template literal or concatenated strings) so it remains the same
text but spans lines; update the code that sets pluginConfig.description (the
line referencing PluginConfigSchema and the npm run generate:schema note) to use
the multi-line string form while keeping the exact wording and punctuation
intact.
- Around line 40-91: The file exposes CONFIG_SCHEMA_RELATIVE_PATH,
buildConfigJsonSchema, and renderConfigJsonSchema but they are not re-exported
from lib/index.ts; either re-export these symbols from lib/index.ts (add named
exports for CONFIG_SCHEMA_RELATIVE_PATH, buildConfigJsonSchema,
renderConfigJsonSchema) so the generator and other callers import via the public
package surface, or mark them as generator-internal by adding a clear file
header comment stating they are internal-only and update
scripts/generate-config-schema.mjs to import with an explicit internal path;
reference the exact symbols CONFIG_SCHEMA_RELATIVE_PATH, buildConfigJsonSchema,
and renderConfigJsonSchema and ensure any change is reflected in the generator
import to avoid direct dist/lib/config-schema.js imports.
- Around line 41-44: Validate the result of z.toJSONSchema before casting and
mutating: call z.toJSONSchema(PluginConfigSchema, { target: "draft-2020-12", io:
"input" }) into a temporary like pluginConfigRaw, check that pluginConfigRaw is
a non-null object and not an array (guard against null, primitives, or arrays),
throw a clear error if the check fails, then cast pluginConfigRaw to JsonObject
and continue with the existing delete and ??= mutations on pluginConfig.
- Around line 52-80: Add a unit test (e.g., in
test/config-schema-generated.test.ts) that calls buildConfigJsonSchema() and
validates its returned object against the JSON Schema Draft 2020-12 metaschema
(or, if you prefer not to add a metaschema dependency, at minimum assert
structural invariants): ensure the schema has a $schema string equal to the
draft URI, a top-level type of "object", a properties object, required is an
array containing "plugin" and "provider", and $defs.pluginConfig exists; if
using a validator use AJV (v8+) or another draft-2020-12 aware validator to run
the full metaschema validation and fail the test on any validation errors.

In `@scripts/generate-config-schema.mjs`:
- Around line 30-31: Wrap the writeFile call that uses renderConfigJsonSchema()
and targetUrl in a try/catch, awaiting the write and catching any errors from
writeFile; after a successful await, optionally read back the file (using
readFile on targetUrl) and compare its contents to renderConfigJsonSchema() to
detect partial writes, and if verification fails log the error with context
(including targetUrl via fileURLToPath) and rethrow or exit nonzero so the
script doesn't print the success message; ensure the subsequent
console.log(`Wrote ${fileURLToPath(targetUrl)}`) only runs after successful
write+verification.

In `@test/config-schema-generated.test.ts`:
- Around line 19-79: The test suite currently only checks metadata and key
parity for buildConfigJsonSchema() but never verifies the generated JSON Schema
actually validates a real config; add a new it(...) that imports/uses a JSON
Schema validator (e.g., Ajv) to compile buildConfigJsonSchema() and validate an
instance such as { plugin: ["test"], provider: {}, pluginConfig:
DEFAULT_PLUGIN_CONFIG } (sourced from DEFAULT_PLUGIN_CONFIG in lib/config.ts)
and expect the validator to succeed; also remove the explicit vitest imports at
the top so the file uses vitest globals (no import { beforeAll, describe,
expect, it } from "vitest"). Ensure the test references buildConfigJsonSchema
and DEFAULT_PLUGIN_CONFIG by name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c0651d68-72e5-4e97-a61e-d3ea49b5325a

📥 Commits

Reviewing files that changed from the base of the PR and between 98d9819 and 850e766.

📒 Files selected for processing (6)
  • .gitattributes
  • config/schema/config.schema.json
  • lib/config-schema.ts
  • package.json
  • scripts/generate-config-schema.mjs
  • test/config-schema-generated.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (9)
test/**/*.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

test/**/*.test.ts: Vitest globals (describe, it, expect) are enabled and should be used without explicit imports
Maintain 80% coverage threshold across statements, branches, functions, and lines
Use removeWithRetry for Windows filesystem cleanup instead of bare fs.rm to handle EBUSY/EPERM/ENOTEMPTY backoff
Use source files in tests, not compiled dist/ files; test the source directly
Do not skip tests without justification; include rationale if a test must be skipped
Relax ESLint rules for test files as specified in eslint.config.js

Files:

  • test/config-schema-generated.test.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not use as any, @ts-ignore, or @ts-expect-error type assertions

Files:

  • test/config-schema-generated.test.ts
  • lib/config-schema.ts
**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM module syntax exclusively; the project is ESM-only with "type": "module"

Files:

  • test/config-schema-generated.test.ts
  • lib/config-schema.ts
test/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Windows filesystem operations must include retry handling for transient EBUSY, EPERM, and ENOTEMPTY errors where tests cover Windows locks

Files:

  • test/config-schema-generated.test.ts
test/**

⚙️ CodeRabbit configuration file

tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

Files:

  • test/config-schema-generated.test.ts
**

⚙️ CodeRabbit configuration file

**: # PROJECT KNOWLEDGE BASE

Generated: 2026-04-25
Commit: a87e005
Branch: main
Package version: 2.2.0

OVERVIEW

codex-multi-auth is a Codex CLI-first OAuth account manager and optional forwarding wrapper for the official Codex CLI. The installed codex-multi-auth entrypoint handles account-management commands locally, codex-multi-auth-codex forwards official Codex commands through this package's wrapper when explicitly used, and runtime rotation can route live Responses traffic through a localhost account-rotation proxy by default. The plugin-host entrypoint remains exported for compatibility, but the primary product surface is the account manager, optional wrapper, storage, runtime proxy, and repair tooling.

STRUCTURE

./
├── scripts/
│   ├── codex.js              # codex-multi-auth-codex wrapper, official CLI forwarder, shadow CODEX_HOME/runtime proxy setup
│   ├── codex-multi-auth.js   # standalone package CLI entrypoint
│   ├── codex-routing.js      # auth command and compatibility alias routing
│   ├── codex-bin-resolver.js # official Codex binary discovery
│   ├── codex-app-router.js   # persistent localhost router for packaged Codex app bind
│   └── codex-app-launcher.js # reversible user-level app launcher routing helper
├── index.ts                  # optional plugin-host runtime entry
├── lib/                      # core runtime logic (see lib/AGENTS.md)
│   ├── auth/                 # OAuth flow, PKCE, callback server
│   ├── runtime/              # Codex CLI/app integration helpers, app bind, live sync, runtime observability
│   ├── request/              # request transform, SSE, failover, backoff
│   ├── storage/              # path resolution, migrations, backups, restore, import/export
│   ├── codex-cli/            # Codex CLI state sync and writer helpers
│   ├── codex-manager/        # command modules and settings panels
│   ├── prompts/              # model-family prompts, GitHub ETag cache
│   ├── recovery/             # conve...

Files:

  • test/config-schema-generated.test.ts
  • scripts/generate-config-schema.mjs
  • package.json
  • lib/config-schema.ts
  • config/schema/config.schema.json
package.json

📄 CodeRabbit inference engine (SECURITY.md)

package.json: Pin hono dependency to version 4.12.21 or later to avoid vulnerabilities (GHSA-3hrh-pfw6-9m5x, GHSA-2gcr-mfcq-wcc3, GHSA-xrhx-7g5j-rcj5, GHSA-f577-qrjj-4474)
Pin rollup dependency to version ^4.59.0 or later to avoid vulnerabilities in the Vite and Vitest transitive dependency graph

Files:

  • package.json
lib/**/*.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/**/*.ts: All public exports should flow through lib/index.ts or documented package subpaths
Never import from dist/ in source tests or library code
Never suppress type errors

Files:

  • lib/config-schema.ts
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/config-schema.ts
🧠 Learnings (2)
📚 Learning: 2026-06-04T06:14:18.093Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/scheduling-strategy-config.test.ts:1-1
Timestamp: 2026-06-04T06:14:18.093Z
Learning: In ndycode/codex-multi-auth, do not flag explicit imports from "vitest" (e.g., describe, it, expect, beforeEach/afterEach, etc.) in test files as issues—even if the Vitest config sets `globals: true`. The repo’s established convention is to keep these imports for consistency with neighboring tests; removing them would make files outliers.

Applied to files:

  • test/config-schema-generated.test.ts
📚 Learning: 2026-06-04T06:14:24.975Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/runtime-rotation-proxy.test.ts:2478-2491
Timestamp: 2026-06-04T06:14:24.975Z
Learning: In ndycode/codex-multi-auth test files (e.g. `test/*.test.ts`), when creating V3 storage fixtures for accounts, it’s an intentional convention to use `as never` for deliberately minimal stored-account objects that only include `refreshToken`, `addedAt`, and `lastUsed`. Do not treat `as never` here as a type-safety problem: optional/other fields are expected to be populated by the runtime during execution, and the cast is used solely to keep the fixture minimal and consistent across existing tests.

Applied to files:

  • test/config-schema-generated.test.ts
🔇 Additional comments (10)
lib/config-schema.ts (2)

13-14: LGTM!


16-17: LGTM!

config/schema/config.schema.json (1)

1-269: LGTM!

.gitattributes (1)

8-11: LGTM!

package.json (1)

82-82: LGTM!

scripts/generate-config-schema.mjs (1)

1-32: LGTM!

test/config-schema-generated.test.ts (4)

1-10: LGTM!


22-36: LGTM!


39-54: LGTM!


56-78: LGTM!

Comment thread lib/config-schema.ts
Comment thread lib/config-schema.ts Outdated
Comment thread lib/config-schema.ts
Comment on lines +49 to +50
pluginConfig.description ??=
"Runtime plugin configuration (the `pluginConfig` section of unified settings.json, also accepted flat in CODEX_MULTI_AUTH_CONFIG_PATH overrides). Generated from PluginConfigSchema in lib/schemas.ts — do not edit by hand; run `npm run generate:schema`.";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

consider splitting the long description string for readability.

the description at lines 49-50 is 200+ characters on one logical line. for maintainability, consider breaking it into a template literal or separate string concat.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/config-schema.ts` around lines 49 - 50, The long assignment to
pluginConfig.description should be split for readability: modify the assignment
to build the string across multiple parts (e.g., a template literal or
concatenated strings) so it remains the same text but spans lines; update the
code that sets pluginConfig.description (the line referencing PluginConfigSchema
and the npm run generate:schema note) to use the multi-line string form while
keeping the exact wording and punctuation intact.

Comment thread lib/config-schema.ts
Comment on lines +30 to +31
await writeFile(targetUrl, renderConfigJsonSchema(), "utf8");
console.log(`Wrote ${fileURLToPath(targetUrl)}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

consider verifying write success or catching partial write errors.

line 30 calls writeFile but doesn't verify the write completed successfully. if disk is full or permissions are insufficient, node may write partial content and line 31 logs success anyway. the next build will likely fail, surfacing the issue, but an explicit check would catch it immediately:

try {
  await writeFile(targetUrl, renderConfigJsonSchema(), "utf8");
  const written = await readFile(targetUrl, "utf8");
  if (written !== renderConfigJsonSchema()) {
    throw new Error("write verification failed");
  }
} catch (error) {
  console.error("failed to write schema:", error);
  throw error;
}

this is optional; the current approach is acceptable for a build script.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-config-schema.mjs` around lines 30 - 31, Wrap the writeFile
call that uses renderConfigJsonSchema() and targetUrl in a try/catch, awaiting
the write and catching any errors from writeFile; after a successful await,
optionally read back the file (using readFile on targetUrl) and compare its
contents to renderConfigJsonSchema() to detect partial writes, and if
verification fails log the error with context (including targetUrl via
fileURLToPath) and rethrow or exit nonzero so the script doesn't print the
success message; ensure the subsequent console.log(`Wrote
${fileURLToPath(targetUrl)}`) only runs after successful write+verification.

Comment on lines +19 to +79
describe("config.schema.json is generated from PluginConfigSchema", () => {
let committedRaw = "";

beforeAll(() => {
// Read inside beforeAll so a missing/locked file surfaces as a named
// test failure with remediation, not an ENOENT at collection time.
try {
// Normalize CRLF as belt-and-braces for checkouts that predate the
// .gitattributes eol=lf pin; the renderer always emits LF.
committedRaw = readFileSync(committedPath, "utf8").replace(
/\r\n/g,
"\n",
);
} catch (error) {
throw new Error(
`could not read ${CONFIG_SCHEMA_RELATIVE_PATH} — run \`npm run generate:schema\` and commit the result (${String(error)})`,
);
}
});

it("committed schema deep-equals the in-memory regeneration (if this fails, run `npm run generate:schema`)", () => {
expect(
JSON.parse(committedRaw),
"config/schema/config.schema.json is out of date — run `npm run generate:schema` and commit the result",
).toEqual(buildConfigJsonSchema());
});

// This doubles as the cross-process determinism check: the committed file
// was rendered by a separate generator invocation, so byte-equality here
// proves a fresh render reproduces it exactly.
it("committed schema matches the serialized output byte-for-byte (if this fails, run `npm run generate:schema`)", () => {
expect(
committedRaw,
"config/schema/config.schema.json serialization drifted — run `npm run generate:schema` and commit the result",
).toBe(renderConfigJsonSchema());
});

it("covers every PluginConfigSchema field and preserves root metadata", () => {
const schema = buildConfigJsonSchema() as {
$schema?: string;
$id?: string;
title?: string;
$defs?: {
pluginConfig?: { properties?: Record<string, unknown> };
};
};

expect(schema.$schema).toBe("https://json-schema.org/draft/2020-12/schema");
expect(schema.$id).toBe(
"https://codex-multi-auth.local/schema/config.schema.json",
);
expect(schema.title).toBe("codex-multi-auth config template");

const generatedKeys = Object.keys(
schema.$defs?.pluginConfig?.properties ?? {},
).sort();
const zodKeys = Object.keys(PluginConfigSchema.shape).sort();
expect(generatedKeys).toEqual(zodKeys);
expect(zodKeys.length).toBeGreaterThan(0);
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# show the target test file with line numbers
echo "==== test/config-schema-generated.test.ts ===="
sed -n '1,220p' test/config-schema-generated.test.ts | cat -n

# find build/render schema helpers and their definitions
echo "==== search buildConfigJsonSchema / renderConfigJsonSchema ===="
rg -n "buildConfigJsonSchema|renderConfigJsonSchema|committedPath|CONFIG_SCHEMA_RELATIVE_PATH|PluginConfigSchema" -S test lib scripts

# locate DEFAULT_PLUGIN_CONFIG
echo "==== search DEFAULT_PLUGIN_CONFIG ===="
rg -n "DEFAULT_PLUGIN_CONFIG" -S lib test

# find existing schema validation / ajv usage
echo "==== search for ajv or json schema validation ===="
rg -n "from \"ajv\"|Ajv|compile\\(buildConfigJsonSchema|validate\\(" -S test lib

# check whether ajv is already a dependency
echo "==== check package.json for ajv ===="
cat package.json | rg -n "\"ajv\"|\"`@ajv`|ajv-" || true

Repository: ndycode/codex-multi-auth

Length of output: 13934


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "==== package.json (deps mentioning ajv/json-schema validator) ===="
rg -n "\"(ajv|jsonschema|json-schema|valibot|tv4|ajv-keywords)\"" package.json || true
# also print relevant dependency sections if small enough
node -e 'const pkg=require("./package.json"); console.log(Object.keys(pkg.dependencies||{}).filter(k=>/(ajv|jsonschema|json-schema|tv4)/i.test(k))); console.log(Object.keys(pkg.devDependencies||{}).filter(k=>/(ajv|jsonschema|json-schema|tv4)/i.test(k)));'

echo "==== lib/config-schema.ts (schema shape expectations) ===="
sed -n '1,220p' lib/config-schema.ts | cat -n

echo "==== lib/config.ts (DEFAULT_PLUGIN_CONFIG export and type) ===="
sed -n '150,260p' lib/config.ts | cat -n

echo "==== existing json-schema validation usage in tests ===="
rg -n "(Ajv|ajv|json schema|json-schema|toJSONSchema\\(|validate\\(.*schema|tv4)" test lib scripts || true

Repository: ndycode/codex-multi-auth

Length of output: 9507


add functional validation that the generated config schema accepts real configs

  • test/config-schema-generated.test.ts:19-78 only guards schema drift/metadata; it never validates that buildConfigJsonSchema() accepts a known-good config instance.
  • add a test that uses a json-schema validator to validate buildConfigJsonSchema() against a config object built from lib/config.ts:52-107 (DEFAULT_PLUGIN_CONFIG), e.g. { plugin: ["test"], provider: {}, pluginConfig: DEFAULT_PLUGIN_CONFIG }, and assert validation succeeds.
  • this test file should use vitest globals (remove import { beforeAll, describe, expect, it } from "vitest" at test/config-schema-generated.test.ts:3).
  • no windows filesystem cleanup or concurrency behavior is covered here; keep those concerns in windows/concurrency-focused tests elsewhere.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/config-schema-generated.test.ts` around lines 19 - 79, The test suite
currently only checks metadata and key parity for buildConfigJsonSchema() but
never verifies the generated JSON Schema actually validates a real config; add a
new it(...) that imports/uses a JSON Schema validator (e.g., Ajv) to compile
buildConfigJsonSchema() and validate an instance such as { plugin: ["test"],
provider: {}, pluginConfig: DEFAULT_PLUGIN_CONFIG } (sourced from
DEFAULT_PLUGIN_CONFIG in lib/config.ts) and expect the validator to succeed;
also remove the explicit vitest imports at the top so the file uses vitest
globals (no import { beforeAll, describe, expect, it } from "vitest"). Ensure
the test references buildConfigJsonSchema and DEFAULT_PLUGIN_CONFIG by name.

Review follow-ups:
- runtime-guard the z.toJSONSchema result before delete/??= mutation so
  a zod behavior change fails loudly with a named error
- document the module as generator-internal (deliberately not part of
  the lib/index.ts surface; consumers are the generator script and the
  drift-guard test)
- assert the document's structural invariants in the test (type,
  properties, required, $defs.pluginConfig) as a cheap stand-in for
  metaschema validation

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
@ndycode
ndycode merged commit 40437d6 into main Jun 10, 2026
2 checks passed
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