fix(cli): reconcile normalized Codex hooks - #1443
Conversation
There was a problem hiding this comment.
🟡 Not ready to approve
There is a confirmed validation bug in the new TOML number parser (signed base integers) and an uninstall-path cleanup gap when hook preflight fails.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR fixes Codex config.toml corruption (duplicate hooks.SessionStart definitions) by adding structural reconciliation of installer-owned SessionStart/SubagentStart hooks across TOML representations, and by introducing a read-only preflight path to fail closed before mutating Codex artifacts.
Changes:
- Add a new TOML reconciliation engine that can detect/repair owned Codex lifecycle hooks in both inline and array-of-tables forms while preserving foreign entries.
- Wire the CLI installer/uninstaller to run a Codex hook preflight and to migrate/clean up TOML-owned hooks when
hooks.jsonis used. - Add regression + matrix tests covering inline, mixed, malformed/ambiguous, and BOM/CRLF scenarios.
File summaries
| File | Description |
|---|---|
| tests/test_config_toml_edit.c | Adds extensive reconciliation and fail-closed regression tests for Codex TOML hook editing. |
| tests/test_cli.c | Adds CLI-level regression tests for issue #1432, read-only preflight, and install/dry-run/idempotency lifecycle. |
| src/cli/config_toml_edit.h | Introduces the Codex hook reconciliation API and mode enum. |
| src/cli/config_toml_edit.c | Implements structural parsing + reconciliation for owned Codex hooks and strengthens collision guards. |
| src/cli/cli.c | Switches Codex hook install/removal to reconciliation API and adds installer/uninstaller preflight gating. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/cli/config_toml_edit.c:4278
edit->replacementcan beNULLwhenreplacement_len == 0(e.g., deletion edits pushed withNULL). This relies ontoml_buffer_append()being safe with aNULLpointer and zero length; that’s not guaranteed unlesstoml_buffer_append()explicitly handleslen == 0without dereferencing the pointer. To make this robust, skip the append whenreplacement_len == 0, or ensuretoml_buffer_append()defensively returns OK forlen == 0regardless ofdatapointer value.
if (edit->start < cursor || edit->end > len ||
toml_buffer_append(output, data + cursor, edit->start - cursor) != TOML_EDIT_OK ||
toml_buffer_append(output, edit->replacement, edit->replacement_len) != TOML_EDIT_OK) {
return TOML_EDIT_ERR;
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/cli/config_toml_edit.c:2919
- The base-prefixed integer branch only triggers when
pos == 0U, so signed hex/octal/binary forms like-0x10/+0o77are treated as invalid. TOML allows an optional sign on integers (including base-prefixed), so this can cause hook reconciliation to fail-closed on valid user configs that include signed base-prefixed numbers inside inline hook objects. Consider accepting the base prefix atpos(after the optional sign) and adjusting the digit start/end accordingly; update the invalid-fixture tests that currently assume signed base-prefixed values are malformed.
static int toml_codex_number_is_valid(const char *data, size_t len) {
size_t pos = 0U;
if (pos < len && (data[pos] == '+' || data[pos] == '-')) {
pos++;
}
if (pos == len) {
return 0;
}
if ((len - pos == 3U && memcmp(data + pos, "inf", 3U) == 0) ||
(len - pos == 3U && memcmp(data + pos, "nan", 3U) == 0)) {
return 1;
}
if (pos == 0U && len >= 3U && data[0] == '0' &&
(data[1] == 'x' || data[1] == 'o' || data[1] == 'b')) {
int base = data[1] == 'x' ? 16 : (data[1] == 'o' ? 8 : 2);
return toml_codex_digits_are_valid(data, 2U, len, base, NULL, NULL);
}
src/cli/config_toml_edit.c:2720
- Duplicate-key detection is O(n) per insertion, which becomes O(n²) for large inline tables/objects. Since
TOML_CODEX_MAX_ITEMSis 65536, a crafted inline hook value with many fields could cause very slow parsing even though you ultimately fail-closed. Consider tightening the maximum expected field count for these Codex-specific objects (e.g., a small fixed upper bound), or switching duplicate detection to something sub-quadratic (e.g., sort once then scan, or a small hash set keyed by the rendered key-path).
static int toml_codex_field_vector_push(toml_codex_field_vector_t *vector, toml_key_path_t *key,
size_t value_start, size_t value_end) {
if (!vector || !key || value_start > value_end || vector->count >= TOML_CODEX_MAX_ITEMS) {
return TOML_EDIT_ERR;
}
for (size_t i = 0U; i < vector->count; ++i) {
if (toml_key_path_equal(&vector->items[i].key, key)) {
return TOML_EDIT_ERR;
}
}
tests/test_cli.c:7947
- These new CLI tests hardcode
/tmp/...for temp directories. This is not portable on Windows (and can be problematic in sandboxed CI), even though other parts of the test suite appear to use helper tempdir utilities. Prefer using an existing cross-platform tempdir helper (or deriving the base temp directory from the environment) and then applying theXXXXXXtemplate under that directory.
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-codex-inline-hook-XXXXXX");
if (!cbm_mkdtemp(tmpdir))
FAIL("cbm_mkdtemp failed");
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
c09be6a to
b3bdef1
Compare
Signed-off-by: astandrik <astandrik@yandex-team.ru>
b3bdef1 to
0cf0ccb
Compare
What does this PR do?
Fixes the Codex configuration corruption reported in #1432 with a narrow ownership-aware reconciler.
SessionStart/SubagentStartinline assignments, including horizontal whitespace and dotted or quoted hook paths.This deliberately does not add a general TOML parser, partial inline-array merging, a Kimi collision guard, new dependencies, or public CLI/MCP APIs. The final diff is 500 production additions and 728 additions overall.
Regression evidence
scripts/test.sh --suites clireported 257 passed and 1 failed on the exact issue fixture.cli_codex_session_hook_issue330failed atstrstr(d, "SessionStart = [") is not NULL, proving install appended a conflicting array-of-tables hook instead of reconciling the normalized inline assignment.scripts/test.sh --suites config_toml_edit,clireports 296 passed under ASan/UBSan on currentorigin/main.cliandconfig_toml_editsuites pass. The remaining failures are process-containment cases in untoucheddaemon_runtime,mcp, andindex_supervisorpaths; an isolated rerun reproduced the latter two and then hung indaemon_runtimewith macOS children inUEstate.Verification
scripts/test.sh --suites config_toml_edit,cliclang-format --dry-run --Werror-Wall -Wextra -Werrorsyntax checkmake -f Makefile.cbm lint-no-suppressgit diff --check origin/main..HEADscripts/check-dco.sh origin/main..HEADmake -f Makefile.cbm lint-ciis locally blocked before source analysis becausecppcheckis not installed; no dependency was installed for this PR. GitHub lint and smoke checks are the authoritative clean-environment runs.Checklist
git commit -s) — required, CI rejects unsigned commitscppcheckis absentRollback
Revert the single focused commit:
git revert 0cf0ccb526cd5e396a8687460edb012ca1a7bcdfCloses #1432