Skip to content

v1.7.1

Latest

Choose a tag to compare

@jdwyah jdwyah released this 09 Oct 20:23
· 3 commits to main since this release

1.7.1 - 2026-10-09

  • Chaos harness: an unknown sdkMetric name 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') == 0 passed 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 answered 200 text/event-stream and 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_change no longer receives a repeated
    connected for 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_context is per asyncio task, not just per thread
    (patch).
    The scope lived in threading.local(), and asyncio tasks share
    one thread, so in FastAPI/Starlette/aiohttp/Django-async handlers two
    requests whose scopes interleaved across an await evaluated 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
    a contextvars.ContextVar and 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_context exited in a different asyncio task no longer
    raises (patch).
    The ContextVar-based scope above restored the outer
    scope with ContextVar.reset(token), which raises ValueError: ... was created in a different Context when the with block 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 old threading.local scope 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 decryptWith variant inside a weighted rollout is
    redacted in telemetry (patch).
    Redaction looked only at the outer
    weighted_values value, so a rollout variant marked confidential (or
    encrypted with decryptWith) reached the telemetry selectedValue in
    plaintext; for decryptWith that 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 for decryptWith), 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: tenacity and packaging are no longer declared
    dependencies (patch).
    The SDK never imported either. If your own code
    imports them and relied on quonfig pulling 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 ran qfg login, the signed-in email from
    ~/.quonfig/tokens.json is 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).