Repository navigation
1.7.1 - 2026-10-09
-
Chaos harness: an unknown
sdkMetricname now fails loudly (none; test
harness only, not shipped in the package). The chaos probe returned 0 for
a metric name it does not implement, so an expectation such as
client.sdkMetric('typo_total') == 0passed without checking anything. The
probe now reports the name as unknown and the expectation fails with
unknown sdkMetric '<name>', matching sdk-go. Every metric name in ITD
v2026.10.03 is implemented, so no current scenario result changes
(qfg-goi1.2.22). -
Fix: SSE no longer reconnects in a tight loop after a clean close
(patch). When the server answered200 text/event-streamand then closed
the stream cleanly (a server FIN, a load balancer recycling the
connection), the client reconnected immediately with no delay, so a server
that kept doing that got thousands of connections per second from one
client. Every reconnect now waits a jittered 0.25-0.5 s first, matching
sdk-go. A clean close does not grow the backoff, and the reconnect stays
silent:on_sse_connection_state_changeno longer receives a repeated
connectedfor it (qfg-goi1.2.12). -
Fix: a config response cut off mid-body no longer stalls the fetch
(patch). The body is read through urllib3, whose errors for a truncated or
reset body (ProtocolError) or a stalled one (ReadTimeoutError) escaped
the per-URL catch. On the hedged path the leg's worker thread died without
reporting, so every fetch (init and each fallback-poll tick) waited out the
full ~15 s drain budget and printed a thread traceback; on the sequential
path the error escaped instead of failing over to the next URL. Every
failure of a URL is now a leg error: the hedge settles at once and fires the
secondary, and the sequential path fails over (qfg-goi1.2.13). -
Fix:
scoped_contextis per asyncio task, not just per thread
(patch). The scope lived inthreading.local(), and asyncio tasks share
one thread, so in FastAPI/Starlette/aiohttp/Django-async handlers two
requests whose scopes interleaved across anawaitevaluated flags with
each other's context, and the scope restored on exit could stay installed
on the event-loop thread for every later evaluation. The scope now lives in
acontextvars.ContextVarand is restored with its token: each task sees
only its own scope, a task created inside a scope inherits it, and sync
threaded code behaves as before (qfg-goi1.2.13). -
Fix: a
scoped_contextexited in a different asyncio task no longer
raises (patch). TheContextVar-based scope above restored the outer
scope withContextVar.reset(token), which raisesValueError: ... was created in a different Contextwhen thewithblock is entered in one
task and exited in another: pytest-asyncio async-generator fixtures (setup
and teardown run as separate tasks) and async generators closed from
another task. The oldthreading.localscope exited cleanly there. The exit
now falls back to restoring the previous scope in the exiting task, so it
never raises (qfg-goi1.2.45). -
Fix: a confidential or
decryptWithvariant inside a weighted rollout is
redacted in telemetry (patch). Redaction looked only at the outer
weighted_valuesvalue, so a rollout variant markedconfidential(or
encrypted withdecryptWith) reached the telemetryselectedValuein
plaintext; fordecryptWiththat was the decrypted secret. The selected
variant now decides, and it is sent as the usual*****<hash>form (hash of
the stored value, i.e. the ciphertext fordecryptWith), matching sdk-go.
Values returned to the caller are unchanged (qfg-goi1.2.13). -
Fix:
close()closes the live SSE connection (patch).close()only
set the shutdown flag, so the SSE thread stayed blocked in its read and the
delivery connection stayed open until the next event or heartbeat (~30 s)
or the 60 s read timeout. Repeated create/close (tests, per-tenant clients)
held that many extra connections.close()now closes the live stream
response, so the connection drops and the SSE thread exits at once
(qfg-goi1.2.13). -
Packaging:
tenacityandpackagingare no longer declared
dependencies (patch). The SDK never imported either. If your own code
imports them and relied onquonfigpulling them in, declare them in your
project directly (qfg-goi1.2.13). -
Docs: the README says that dev-context injection is on by default. On a
machine where someone ranqfg login, the signed-in email from
~/.quonfig/tokens.jsonis added to every evaluation as
quonfig-user.email(and so reaches telemetry example contexts); the
README now names the two ways to turn it off (enable_quonfig_user_context=False,
QUONFIG_DEV_CONTEXT=false). No behavior change (qfg-goi1.2.13).