fix: cap adversarial review prompt size to stay under Codex API 1MB input limit#313
Open
cardene777 wants to merge 1 commit into
Open
Conversation
…nput limit The `adversarial-review --wait` command fails on heavy PRs with `Input exceeds the maximum length of 1048576 characters.` even after PR openai#179 capped the embedded diff to 256KB. The Codex API thread input is hard-limited to 1,048,576 characters, and the template plus 256KB diff plus collection guidance plus focus text can still cross that line in practice, especially when the diff contains multi-byte UTF-8. Add `MAX_ADVERSARIAL_PROMPT_BYTES = 850 * 1024` and `MAX_ADVERSARIAL_PROMPT_CHARS = 900 * 1024` budgets and a small fallback chain inside `buildAdversarialReviewPrompt`: 1. render the full prompt and return it if it already fits both budgets; 2. otherwise binary-search the largest prefix of `REVIEW_INPUT` that fits, append a truncation notice, and re-render; 3. if no useful prefix survives, drop the inline diff entirely and switch the collection guidance to the existing lightweight self-collect path. Both budgets are checked together, so a prompt of mostly multi-byte content (e.g. emoji-heavy diffs) is constrained by chars while a long ASCII diff is constrained by bytes. `USER_FOCUS` is never dropped, since it is small and high-signal. The helper is now exported alongside the budget constants so it can be unit-tested directly. The bottom-of-file `main()` call is guarded behind `isDirectInvocation()` so importing the module from tests no longer runs the CLI entry point. New tests in `tests/adversarial-prompt-cap.test.mjs` cover: small input passthrough, ~900KB ASCII content truncation, truncation marker presence, unfittable (5MB) input falling back to self-collect mode, multi-byte content respecting both budgets, `USER_FOCUS` preservation, and a sanity check that the byte/char budgets stay under the 1,048,576-char API cap. Refs: cardene777/claude-config#1467
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/codex:adversarial-review --wait(and thereforeSkill('parallel-review', ...)flows that wrap it) fails on heavy PRs with:even after PR #179 capped the embedded diff at 256KB. PR #179 fixed the Node
spawnSyncENOBUFS path, but the Codex API thread input is hard-limited to 1,048,576 characters, andtemplate + 256KB diff + collection guidance + focus textcan still cross that line in practice — especially when the diff contains multi-byte UTF-8.This PR adds a small second-tier budget inside
buildAdversarialReviewPromptso the prompt as a whole always fits the API window.Repro
Reported downstream as cardene777/claude-config#1467 and triggered on AlphaTrader heavy-tier PR
+540/-31. Observed exit:focus_textshort-circuit (200 → 100 chars) did not change the outcome, which is what pointed at the assembled prompt rather than the focus string.Change
Inside
plugins/codex/scripts/codex-companion.mjs:MAX_ADVERSARIAL_PROMPT_BYTES = 850 * 1024MAX_ADVERSARIAL_PROMPT_CHARS = 900 * 1024Both checked together so emoji-heavy diffs are clamped by chars and long ASCII diffs are clamped by bytes. Both stay safely under the 1,048,576-char API cap so the system-prompt / thread metadata that the API stitches on every turn has headroom.
buildAdversarialReviewPromptinto a three-step fallback chain:REVIEW_INPUTthat fits, append a[truncated: REVIEW_INPUT was trimmed by N bytes ...]notice, and re-render;REVIEW_COLLECTION_GUIDANCEto the existing lightweight self-collect language so the reviewer is told to use read-onlygitcommands.USER_FOCUSis never dropped — it is small and high-signal.buildAdversarialReviewPrompt,MAX_ADVERSARIAL_PROMPT_BYTES, andMAX_ADVERSARIAL_PROMPT_CHARSso they are unit-testable.main()invocation behindisDirectInvocation()so importing the module from tests no longer runs the CLI entry point. This is the minimal side-effect cleanup that the export change made necessary.Tests
New
tests/adversarial-prompt-cap.test.mjs(7 cases, all green locally):USER_FOCUSFull
npm testresult on this branch: 89 passed / 4 failed. The 4 failures are pre-existing and reproduce on a cleanmaincheckout (resolveStateDir uses a temp-backed per-workspace directory,result returns the stored output for the latest finished job by default, and twostatusruntime tests that depend on a live Codex job). They are unrelated to this change.Compatibility
prompts/adversarial-review.mdtemplate wording. Only the chosenREVIEW_INPUTandREVIEW_COLLECTION_GUIDANCEvalues can change when a fallback fires.Non-goals
DEFAULT_INLINE_DIFF_MAX_BYTES(still 256KB; this PR layers an upper-bound budget on top, the two are orthogonal).generator-verifier/codex-reviewpaths — they were never affected.Smoke (pending downstream)
End-to-end smoke on the original heavy-tier repro PR requires a Claude Code restart so the updated plugin gets loaded; that will be run in a follow-up session and reported on cardene777/claude-config#1467. The unit test coverage above exercises every fallback branch directly without needing a live API.
Refs: cardene777/claude-config#1467