Skip to content

Pin openai below 3: its truststore dependency breaks SSL under gevent - #191

Merged
CoffeePoweredComputers merged 1 commit into
masterfrom
fix/pin-openai-below-3
Oct 16, 2026
Merged

CoffeePoweredComputers merged 1 commit into
masterfrom
fix/pin-openai-below-3

Conversation

@CoffeePoweredComputers

Copy link
Copy Markdown
Owner

Production outage

AI grading in production has been failing with:

AI generation failed: SSLSocket.do_handshake() takes 1 positional argument but 2 were given

Cause

requirements.txt uses lower bounds only, so every image build pulls the newest of everything. OpenAI SDK 3.30 was published on 2026-10-14 and every image built since (all of today's deploys, starting with #182) picked up 3.x. OpenAI 3.x depends on a new HTTP stack plus truststore, which wraps the SSL socket with a do_handshake() whose signature does not match what gevent's patched SSLSocket calls. The Celery worker runs with --pool=gevent and monkey-patches ssl, so every LLM request died in the TLS handshake.

Bisect

Reproduced in isolated virtualenvs under monkey.patch_all() against api.openai.com with a dummy key. A healthy stack returns the expected 401; a broken one raises in the handshake.

gevent openai result
26.9.0 (image) 3.31.0 (image) handshake TypeError
26.9.0 2.16.0 401, works
25.9.1 3.31.0 handshake TypeError

The SDK is the variable, not gevent.

Fix

Pin openai>=1.40.6,<3. The code uses only openai.OpenAI(...) and chat.completions, which 2.x provides. No code change.

Follow-up worth doing

Lower-bound-only requirements mean any upstream release can break a deploy with no code change. A lock file (pip-compile or equivalent) would make image builds reproducible.

🤖 Generated with Claude Code

Every production image built since 2026-10-14 pulled openai 3.x, whose
new HTTP stack depends on truststore. truststore wraps the SSL socket with
a do_handshake() that does not match what gevent's patched SSLSocket
calls, so every LLM request under the gevent Celery pool failed with
"SSLSocket.do_handshake() takes 1 positional argument but 2 were given"
and students saw "AI generation failed".

Reproduced in isolated virtualenvs against api.openai.com with a dummy
key, under monkey.patch_all(): gevent 26.9.0 + openai 3.31.0 fails;
gevent 26.9.0 + openai 2.16.0 returns the expected 401; gevent 25.9.1 +
openai 3.31.0 still fails, so the SDK is the variable, not gevent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@CoffeePoweredComputers
CoffeePoweredComputers merged commit 177b45f into master Oct 16, 2026
18 checks passed
@CoffeePoweredComputers
CoffeePoweredComputers deleted the fix/pin-openai-below-3 branch October 16, 2026 22:06
CoffeePoweredComputers added a commit that referenced this pull request Oct 16, 2026
…ments.txt lock

requirements.txt was lower bounds only, so every image build installed
whatever was newest on PyPI that day. That is how openai 3.x reached
production unreviewed and broke AI grading for a day (#191).

- requirements.in now holds the direct dependencies with their safety
  floors (the old file, header rewritten, one duplicate removed).
- requirements.txt is generated from it by `make lock` (uv pip compile,
  resolved for Python 3.11 with universal markers so the 3.12 CI matrix
  and local 3.14 install the same pins). Docker, CI, e2e and the README
  keep installing requirements.txt unchanged.
- CI's lint job recompiles without --upgrade and fails if the lock is
  stale, so editing requirements.in without running `make lock` cannot
  land.

The locked set was installed into a real Python 3.11 and the LLM client
was exercised under gevent monkey-patching: expected 401, no handshake
error.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CoffeePoweredComputers added a commit that referenced this pull request Oct 16, 2026
…ments.txt lock (#192)

requirements.txt was lower bounds only, so every image build installed
whatever was newest on PyPI that day. That is how openai 3.x reached
production unreviewed and broke AI grading for a day (#191).

- requirements.in now holds the direct dependencies with their safety
  floors (the old file, header rewritten, one duplicate removed).
- requirements.txt is generated from it by `make lock` (uv pip compile,
  resolved for Python 3.11 with universal markers so the 3.12 CI matrix
  and local 3.14 install the same pins). Docker, CI, e2e and the README
  keep installing requirements.txt unchanged.
- CI's lint job recompiles without --upgrade and fails if the lock is
  stale, so editing requirements.in without running `make lock` cannot
  land.

The locked set was installed into a real Python 3.11 and the LLM client
was exercised under gevent monkey-patching: expected 401, no handshake
error.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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