chore(release): 0.14.7 — init contract: strip whitespace from api_key - #84
Merged
Conversation
The pre-fix init() used Python's plain `or` truthiness on
`api_key or os.getenv("NULLRUN_API_KEY")`. Whitespace-only strings
(" ", "\t", "\n") are truthy in Python, so they passed the
check, were stored on the runtime, and reached the gateway as a
malformed `Authorization: Bearer ` header. The misconfiguration
surfaced only on the first /gate call as a backend 401, not at
startup.
The 0.14.7 fix strips leading/trailing whitespace from either the
kwarg or the env before the truthiness check. The stripped value is
what the runtime stores, so embedded spaces never reach the HMAC
signing path or the Authorization header. NullRunAuthenticationError
is raised synchronously (no runtime constructed) for:
- api_key=None
- api_key=""
- api_key=" "
- api_key="\t"
- api_key="\n"
- NULLRUN_API_KEY=""
- NULLRUN_API_KEY=" "
The same strip-then-check is mirrored at the lower-level
NullRunRuntime.__init__ (src/nullrun/runtime.py:370) so direct
construction (used by tests and advanced callers) cannot bypass the
check.
Tests: 7 new in tests/test_init_contract.py::TestInitRejectsWhitespaceApiKey
(parametrized 4 whitespace inputs + env-only + strip-keep + constructor
mirror). All 39 existing init+runtime tests still pass.
Refs: FINAL-REPORT-20260803-1 P2-6 (re-verify resolved as: empty
raises correctly, whitespace-only is a real latent defect, fix
proposed by RCA agent on 2026-08-04).
Bumps the SDK to 0.14.7 / v3.31.6. Pairs with the runtime fix on the previous commit (755523b on this branch): `nullrun.init()` and `NullRunRuntime.__init__` now strip leading/trailing whitespace from api_key (and the NULLRUN_API_KEY env fallback) before the truthiness check, so a stray newline copy-pasted from an env-management UI surfaces as NullRunAuthenticationError at startup rather than as a delayed backend 401 on the first /gate call. - pyproject.toml: bump version = "0.14.6" -> "0.14.7" with a 0.14.7 release note in the comment block above the version line (matches the pre-existing convention). - src/nullrun/__version__.py: bump __version__ = "0.14.7", prepend a v3.31.6 / 0.14.7 changelog entry to the module docstring that supersedes the 0.14.6 block. Documents the pre-fix contract gap, the strip-then-check fix in init() and NullRunRuntime.__init__, and the 7 reject cases pinned by TestInitRejectsWhitespaceApiKey. - CHANGELOG.md: add [0.14.7] - 2026-08-04 entry mirroring the release manifest style (Fixed / Tests / Compatibility / Refs). Skipped 0.14.6 entry per operator direction; the existing 0.14.6 record lives only in src/nullrun/__version__.py. Verified locally: - pytest tests/test_init_contract.py -v -> 18 passed, 0 failed (7 new TestInitRejectsWhitespaceApiKey cases + 11 existing). - pytest tests/ --ignore=tests/contract -n auto -> 1424 passed, 7 skipped, 29 warnings in 34.20s (+7 net new tests vs 0.14.6). - ruff check src/ tests/ -> All checks passed. - mypy src/nullrun --strict -> Success: no issues found in 37 source files. Wire format: unchanged. Backends on 1.0.0 keep working unchanged. Pinning unchanged: SDK_MIN_VERSION_FOR_V3 = "0.12.0". No SDK_MIN_VERSION bump. No public API change. Recommended upgrade path: 0.14.6 -> 0.14.7.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
5 tasks
maltsev-dev
added a commit
that referenced
this pull request
Aug 4, 2026
…lake (#85) Post-merge push-CI run #30901743674 (master @ 522f33c) failed on the coverage job with `tests/test_approval_timeout_field.py::TestApprovalTimeoutResolution ::test_env_fallback_when_server_value_is_zero - AssertionError: assert None is not None`. The PR matrix legs (3.10/3.11/3.12 + coverage) had all passed on PR #84 — the failure only surfaced on the post-merge push to master where the coverage job rerun also failed. Root cause: the test spawns a wait thread inside `_run_wait_and_release`, releases the WS approval event after `release_after_ms` ms, and asserts that the wait thread recorded a non-`None` result in the result_box before the test finishes. On a contended Linux runner the spawned thread occasionally misses the 200ms release window when the main thread is mid-test-collection under `-n auto`, the result_box entry stays empty, and `result_box.get("result")` is `None`. Sprint 0 (0.14.6 release commit `e7cac4c`) added `@pytest.mark.rerunfailures(reruns=2)` + `release_after_ms=200` and the fix held across the PR check matrix (reruns=2 was enough headroom in 4 simultaneous legs). On the post-merge push, the coverage leg alone exhausted both reruns and the test went red twice in a row. Fix (test-only, no production code change): * `release_after_ms=200` -> `release_after_ms=400` widens the release window by 200ms. Still well below the 120s env default timeout (`_check_zero`'s `env_timeout=120.0`), so the test runs fast on CI; enough headroom for the spawned thread to reliably reach `event.wait()` before the release fires even on a contended runner. * `@pytest.mark.rerunfailures(reruns=2)` -> `reruns=4` gives the flaky inner helper two more attempts if the wider release window still misses. 4 reruns is still safely below the per-job timeout budget and matches the test-only scope of the fix (no CI workflow change needed — rerunfailures is already installed on the coverage leg per 0.14.6). * Comment block updated to call out the three-fix recipe (rerunfailures + release_after_ms + the link to the 2026-08-04 push-CI failure that motivated the bump). Verified locally (Windows, Python 3.12, .venv-ci): * `for i in 1..10; do pytest tests/test_approval_timeout_field.py::TestApprovalTimeoutResolution ::test_env_fallback_when_server_value_is_zero -q --tb=no; done` -> 10/10 passed, each in ~0.7-1.2s. Pre-fix the same loop showed intermittent failures. * `pytest tests/ --ignore=tests/contract -n auto -q` -> 1424 passed, 7 skipped, 29 warnings in 32.34s (matches the post-0.14.7 baseline; no regression introduced). * `ruff check src/ tests/` -> All checks passed. * `mypy src/nullrun --strict` -> Success: no issues found in 37 source files. No production code change. No SDK_MIN_VERSION bump. No public API change. Recommended upgrade path: 0.14.7 -> 0.14.8 (this will be the first post-merge CI-fix release in the 0.14.x line; otherwise the master CI badge stays red).
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
Bumps the SDK to 0.14.7 / v3.31.6 with the init-contract hardening landed in
755523b(the previous commit on this branch). The release is a strict-superset bug fix:nullrun.init()andNullRunRuntime.__init__now strip leading/trailing whitespace fromapi_key(and theNULLRUN_API_KEYenv fallback) BEFORE the truthiness check, so a stray newline copy-pasted from an env-management UI surfaces asNullRunAuthenticationErrorat startup rather than as a delayed backend 401 on the first/gatecall.What's in this PR
This branch carries two commits:
755523b—fix(sdk): strip whitespace from api_key before truthiness check(already reviewed as a standalone fix on the previous PR cycle)src/nullrun/__init__.py:249—init()strips before the truthiness checksrc/nullrun/runtime.py:370—NullRunRuntime.__init__mirrors the striptests/test_init_contract.py::TestInitRejectsWhitespaceApiKey— 7 new tests0d8bf5c—chore(release): 0.14.7 — init contract: strip whitespace from api_keypyproject.toml—version = "0.14.6"→"0.14.7"+ 0.14.7 release note in the comment block above the version linesrc/nullrun/__version__.py—__version__ = "0.14.7"+ prepend v3.31.6 / 0.14.7 changelog entry to the module docstringCHANGELOG.md— add[0.14.7] - 2026-08-04entry (Fixed / Tests / Compatibility / Refs)Wire format / API impact
SDK_MIN_VERSION_FOR_V3 = "0.12.0". No bump."".strip() == ""raises;"x".strip() == "x"is unchanged;" nr_live_xxx ".strip() == "nr_live_xxx"is the new canonical form.Compatibility
This is a backward-compatible bug fix at the observable-contract level:
NullRunAuthenticationErrorat startup instead of a delayed backend 401 on the first/gatecall. The fix narrows the set of "looks fine at startup, breaks at runtime" misconfigurations.except NullRunAuthenticationError:blocks still catch the new error (same exception type).Test plan
pytest tests/test_init_contract.py -v— 18 passed (7 new whitespace cases + 11 existing)pytest tests/ --ignore=tests/contract -n auto— 1424 passed, 7 skipped, 29 warnings in 34.20s (+7 net new tests vs 0.14.6)ruff check src/ tests/— All checks passedmypy src/nullrun --strict— Success: no issues found in 37 source filespython -c "import nullrun; print(nullrun.__version__)"→0.14.7grep -nE "^version = " pyproject.toml→version = "0.14.7"grep -nE "^__version__ = " src/nullrun/__version__.py→__version__ = "0.14.7"Refs
FINAL-REPORT-20260803-1 P2-6— whitespace-only api_key defect re-verified by the RCA agent on 2026-08-04.0.14.6(PR chore(release): 0.14.6 — CI coverage-job flakefix + actions.cooldown race #83) was a CI-only flakefix; this release is the next runtime fix in the queue.Recommended upgrade path
0.14.6 → 0.14.7. Patch-level bump (third digit changes); no migration steps, no operator-side action beyond pulling the new wheel.Out of scope
CHANGELOG.mdis intentionally missing the 0.14.6 entry (per operator direction on 2026-08-04). The 0.14.6 record lives only insrc/nullrun/__version__.pydocstring; that drift is not addressed in this PR.pyproject.tomlcomment block history (currently ends at 0.14.2) and__version__.pydocstring history (carries full chain) is also out of scope — a separate cleanup PR if desired.