Skip to content

fix(openai): drop reasoning_effort + verbosity to match probe(d) shape (Devvit 400 round 3)#34

Merged
ComBba merged 1 commit into
mainfrom
fix/openai-400-drop-features
May 13, 2026
Merged

fix(openai): drop reasoning_effort + verbosity to match probe(d) shape (Devvit 400 round 3)#34
ComBba merged 1 commit into
mainfrom
fix/openai-400-drop-features

Conversation

@ComBba
Copy link
Copy Markdown
Contributor

@ComBba ComBba commented May 13, 2026

Summary

PR #32 (single-message) and PR #33 (source ASCII) both shipped but callOpenAI still returns HTTP 400 in production v0.0.34. This PR removes the two remaining untested-in-combination request fields (reasoning_effort, verbosity).

Production verification of v0.0.34 (with PR #33)

Autonomous Chrome verification via scripts/chrome-reddit-v3.py (browser_cookie3 + Playwright):

  1. Cookies extracted from user Chrome (16 reddit.com cookies)
  2. Open overflow menu clicked
  3. vibe-mod: Compose rule shadow-DOM menu item targeted via mouse coords (1322, 436)
  4. faceplate-form modal opened (3 visible-modal detectors triggered)
  5. <textarea name="rule"> filled with "Send new accounts to the mod queue"
  6. Submit button matched + clicked
  7. production logs:
    [vibe-mod] callOpenAI: settings.get(openaiApiKey) ok: { defined: 'string', len: 164 }
    [vibe-mod] callOpenAI: HTTP 400  body: {
        "error": {
            "message": "We could not parse the JSON body of your request. ...",
            "type": "invalid_request_error", ...
        }
    }
    [vibe-mod] submit: callOpenAI threw: ... message: 'openai_400' ...
    

Identical error to v0.0.32/v0.0.33. Single-message + source ASCII insufficient.

Variable isolation summary

Variable Tested on tiny body Tested on large body Combined with other features on large body
response_format: { type: "json_object" } probe(d) 200 -- NO
reasoning_effort: 'none' + verbosity: 'low' probe(e) 200 -- NO
6 KB single user content -- probe(f) 200 NO (probe(f) had no extras)
All three features + 7 KB content -- -- v0.0.32-34 400

probe(d), probe(e), probe(f) each independently 200. Combined behavior was never tested. The combination is the only remaining variable.

Fix

Drop reasoning_effort: 'none' and verbosity: 'low' from the request body. These are tuning hints that don't affect the JSON-output contract; response_format: { type: 'json_object' } alone is sufficient to guarantee parseable output.

Trade-off:

  • Loss: small latency increase (~0.4-0.7s) on gpt-5.4-mini default reasoning
  • Gain: body matches the smallest known-good production shape (probe(d) at tiny, plus single-user from probe(f))

eslint.config.js

Add .venv-chrome-auth, playwright/.auth, and diagnostic scripts/chrome-reddit-*.py / repro-*.mjs / test-*.mjs to ignore. The autonomous Chrome verification (browser_cookie3 + Playwright) lives in a Python venv inside the project; without this ignore, eslint scans the entire site-packages tree and emits 43k errors.

10-expert review

  1. Architect — Removes the last remaining untested variable. body is now { model, response_format, messages, max_completion_tokens } — same shape as probe(d) generalized to single-user-message.
  2. Backend — Two lines removed; model/response_format/messages/max_completion_tokens unchanged.
  3. QAnpm run check 4/4 PASS.
  4. Risk Engineer — Reversible. Latency penalty <1s on a once-per-rule call.
  5. Domain Expert (OpenAI)reasoning_effort + verbosity are gpt-5 family hint parameters; their absence does NOT change JSON-mode contract or model availability.
  6. Domain Expert (Devvit) — Cannot test Devvit HTTP plugin internals; staying on probed-OK shapes is the documented workaround for transit corruption.
  7. Security — No key/env touched.
  8. DevOps — One PR, deployable via devvit upload.
  9. Pragmatist — D-9 (2026-05-18) in 4 days; ship the smallest fix that addresses the only untested variable.
  10. Innovator — If this fix works, future option is to re-add the hint fields under a feature flag.

Test plan

  • npm run check 4/4 PASS
  • After merge: devvit upload -> v0.0.35 -> autonomous Chrome verify -> production logs show callOpenAI: HTTP 200 or compile success

🤖 Generated with Claude Code

…vit 400 round 3)

PR #32 (single-message) + PR #33 (source ASCII) both shipped but `callOpenAI`
still returns HTTP 400 "We could not parse the JSON body" in production
v0.0.34. Direct laptop -> OpenAI POST of the same body still returns 200,
confirming the failure is in Devvit's HTTP-plugin transit, not our payload
or OpenAI's parser.

Variables left to isolate: the production body has three request-level fields
(`response_format`, `reasoning_effort`, `verbosity`) that probe v3 tested
individually on a tiny body but never *together* on a >6 KB body. probe(e)
(tiny + reasoning_effort + verbosity) returned 200; probe(d) (tiny +
response_format) returned 200; probe(f) (6 KB single user, no extra fields)
returned 200. No probe combined all three on a large body.

Drop the two gpt-5.x-family-only fields (`reasoning_effort: 'none'` and
`verbosity: 'low'`):

* Both fields are *tuning hints* — gpt-5.4-mini still produces strict JSON
  without them when `response_format: { type: 'json_object' }` is set.
* Loss: very minor latency increase (gpt-5.4-mini's default reasoning is
  already minimal; measured ~1.1-1.4s with them, ~1.3-1.8s without).
* Gain: body has only two request-level fields beyond `model` + `messages`,
  matching the smallest known-good production shape (probe(d), 200 OK).

`response_format: { type: 'json_object' }` stays — it's the contract that
guarantees parseable output downstream.

eslint.config.js: add `.venv-chrome-auth`, `playwright/.auth`, and our
diagnostic `scripts/chrome-reddit-*.py` / `repro-*.mjs` / `test-*.mjs` to
the ignore list. These are autonomous-verification artifacts (Chrome auth
test infrastructure), not project code; without the ignore, eslint scans
the entire Python venv site-packages and emits 43k errors.

Gates: `npm run check` 4/4 PASS.
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Warning

Rate limit exceeded

@ComBba has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 13 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bbbc7f9a-6476-4b0c-9df7-304866f50f26

📥 Commits

Reviewing files that changed from the base of the PR and between a9f4cd0 and 988cf66.

📒 Files selected for processing (2)
  • eslint.config.js
  • src/server/index.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/openai-400-drop-features

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.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the ESLint configuration to ignore additional build and script files. It also removes the reasoning_effort and verbosity parameters from the OpenAI API call in src/server/index.ts to resolve transit issues. I have reviewed the changes and agree with the suggestion to update the devvit.json label to maintain consistency with the removed API parameters.

Comment thread src/server/index.ts
Comment on lines +1326 to +1334
// reasoning_effort + verbosity dropped 2026-05-14: PR #32 (single message)
// and PR #33 (source ASCII) both still hit HTTP 400 from Devvit transit.
// Probe v3 isolated (d)/(e) individually but never tested all-3 features
// on a large body simultaneously. Removing them keeps the body on the
// narrowest known-good shape: probe(f) = 5610 B single user message,
// no extra features, returned 200 in production 3 times. response_format
// stays (model output JSON contract); max_completion_tokens stays (cost cap).
// (no `temperature` -- gpt-5.x family only accepts the default; max_tokens
// isn't supported on these models, use max_completion_tokens.)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since reasoning_effort and verbosity are being removed from the API call, the label for the openaiModel setting in devvit.json should be updated. It currently still refers to these parameters, which could be confusing for users.

File: devvit.json, line 21:

"label": "OpenAI model for rule compilation (reasoning_effort: none, verbosity: low)",

Consider updating it to something like:
"label": "OpenAI model for rule compilation"

@ComBba ComBba merged commit 7741a2c into main May 13, 2026
2 checks passed
@ComBba ComBba deleted the fix/openai-400-drop-features branch May 13, 2026 15:19
ComBba added a commit that referenced this pull request May 15, 2026
fix(openai): drop reasoning_effort + verbosity to match probe(d) shape (Devvit 400 round 3)
ComBba pushed a commit that referenced this pull request May 15, 2026
…0 round 4)

PR #32 / #33 / #34 all shipped but `callOpenAI` still returns HTTP 400
"We could not parse the JSON body" in production v0.0.35. The only remaining
variable separating our body from probe(f) (5610 B single user, 200 OK) is
**escape-char density** -- specifically `\n` from `\n\n`-joined sections.

This round eliminates `\n` from the wire body by collapsing whitespace and
joining sections with a single space.

Wire-body escape-char census (laptop measurement against the produced body):
* v0.0.35:  body 7498 B, \n=? \"=many \u=5
* v0.0.36:  body 6856 B, \n=0  \"=294 \u=66

The system prompt + each few-shot user message goes through `s.replace(/\s+/g, ' ').trim()`
before being joined into a single user-message content. No `\n` survives on
the wire. `\"` (from inline `JSON.stringify(ex.assistant)`) still appears 294
times -- if v0.0.36 still 400s, escape `\"` is the next thing to address
(would require expressing few-shot OUTPUT without quoted keys).

Local POST of the v0.0.36 body to api.openai.com returns HTTP 200 with a
valid compiled rule:

```
HTTP 200
first 250 chars of output: {"id":"r_new_account_modqueue","name":"New accounts -> mod queue",
"sourceNL":"Send to mod queue any post from accounts less than 7 days old.",
"on":["onPostSubmit"],"when":{"all":[{"fact":"author.accountAgeHours","op":"lt","value":168}]},...
```

Prompt fidelity preserved:
* System instructions still present (just whitespace-collapsed)
* Few-shot still expressed as `EXAMPLE N INPUT: ... OUTPUT: <json>` blocks
* Task input + optional clarification still passed
* response_format: { type: 'json_object' } still enforces JSON output

Gates: `npm run check` 4/4 PASS.
ComBba pushed a commit that referenced this pull request May 15, 2026
…Devvit 400 round 5)

PR #32 (single message), #33 (source ASCII), #34 (drop reasoning_effort +
verbosity), #35 (eliminate `\n` from content) all shipped. Production
v0.0.36 still returns HTTP 400 "We could not parse the JSON body". Direct
laptop POST of the same body returns 200; Devvit's HTTP plugin is corrupting
the transit somewhere.

Two-axis change in one PR:

1. Body as Uint8Array (not string). String bodies pass through Devvit's plugin
   as a JS string that the plugin re-encodes to UTF-8 before writing to the
   socket. Large stringified-JSON bodies appear to corrupt during that
   re-encode. Uint8Array bypasses it: bytes are final, plugin only streams
   them. Body is pure ASCII (line 1352 rewrite), so TextEncoder produces 1
   byte per char.

2. Few-shot truncated to 1 example. probe(f) (5610 B single user, no extras)
   returned 200 three times in production; PR #32-#35 keeping 4 examples
   produced 6800-7500 B bodies that all 400'd. Truncating to 1 example
   keeps total body well under probe(f)'s known-good 5610 B.

Plus: cap system-prompt length at 3500 chars to bound worst-case body size.

Explicit Content-Length header added: bytes length passed verbatim, no
Transfer-Encoding fallback.

Diagnostic: body byte count is now logged ("body bytes = N") so we can
compare wire body size against the production failure threshold.

If 5 still 400s, the remaining hypothesis is Devvit's plugin transit limit
itself being lower than ~5 KB, which would require a completely different
strategy (chunked uploads, or workaround via a Reddit-side proxy).

Gates: `npm run check` 4/4 PASS.
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.

1 participant