Skip to content

test(read): v4 Live read cassettes + fix unbounded v4 retry-loop#457

Merged
axisrow merged 1 commit into
mainfrom
fix/v4-read-cassettes-454
May 29, 2026
Merged

test(read): v4 Live read cassettes + fix unbounded v4 retry-loop#457
axisrow merged 1 commit into
mainfrom
fix/v4-read-cassettes-454

Conversation

@axisrow

@axisrow axisrow commented May 29, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Завершает offline-покрытие read-команд: 7 v4 Live read-команд теперь записаны в
VCR-кассеты и реплеятся в CI без токена и сети — так же, как 32 v5-кассеты из #455.

Корень «зависания» (опровержение гипотезы #454)

Issue #454 предполагал, что vcrpy не умеет перехватывать v4 Live HTTP-клиент.
Это неверно:

  1. vcrpy уже записывал v4 Live-кассеты в этом репо (коммит 547e837) тем же
    @pytest.mark.vcr. Транспорт v4 = транспорт v5 (requests/urllib3).
  2. Настоящая причина — баг в V4LiveClientAdapter.retry_request: ретрай ошибок
    request-limit (код 54/55) был без ограничения числа попыток
    (time.sleep(10); return True без проверки repeat_number) → бесконечный цикл.
  3. Sandbox-аккаунт без v4-прав отдаёт код 54 «Not enough rights» (v4 перегружает
    этот код и для rate-limit), из-за чего адаптер вис навсегда. Recording-аккаунт
    (профиль) v4-права имеет — на нём все 7 команд отвечают за пару секунд.

Подтверждено faulthandler-трейсом: до фикса v4goals get-stat-goals против аккаунта
без прав виснет на adapter.py:180; после фикса — 5 ограниченных ретраев и аккуратная
ошибка (exit 1). Это чинит и реальный production-зависон для любого вызывающего.

Изменения

  • direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py — ограничил ветку 54/55 тем же
    бюджетом попыток, что и серверную. ⚠️ Это вендоренный файл: фикс нужно также внести
    в форк axisrow/tapi-yandex-direct
    , иначе он потеряется при следующем update_vendor.sh.
  • tests/test_read_cassettes.py — +6 success-кейсов v4 в READ_CASES
    (v4goals get-stat-goals, v4tags get-campaigns, v4forecast list,
    v4wordstat list-reports, v4events get-events-log, v4finance get-clients-units)
    • отдельный тест check-payment, проверяющий детерминированную ошибку error_code=370
      (exit≠0). Docstring исправлен — убрано неверное утверждение про vcrpy×v4.
  • tests/conftest.py — скраб логина и из тела запроса v4
    (GetClientsUnits шлёт логин аккаунта в Logins).
  • 7 новых кассет против --sandbox, все секреты/PII вычищены: токен в теле,
    логин в request-param и в response-поле Login, auth-заголовки.

Проверка

  • pytest tests/test_read_cassettes.py39 passed (32 v5 + 7 v4) офлайн, без сети.
  • pytest -m "not integration"1963 passed, 46 skipped (вендор-фикс ничего не сломал).
  • ruff / black → clean. _vendor/ исключён из CI mypy.
  • Секрет/PII-свип по всем кассетам (raw + \uXXXX-escaped) → ALL CLEAN.

Follow-up

  • Апстрим фикса retry-loop в форк axisrow/tapi-yandex-direct (чтобы пережил ре-вендоринг).

Closes #454

🤖 Generated with Claude Code

…454)

The 7 v4 Live read commands could not be recorded into VCR cassettes —
`pytest --record-mode=rewrite` "hung indefinitely". Root cause was NOT a
vcrpy×v4 incompatibility (vcrpy records v4 through the same requests/urllib3
transport as v5; it had recorded v4 cassettes before in 547e837). The real
cause: `V4LiveClientAdapter.retry_request` retried request-limit errors
(code 54/55) in an UNBOUNDED loop — `time.sleep(10); return True` with no
`repeat_number` guard. When the sandbox account lacked v4 rights it returned
code 54 ("Not enough rights", which v4 overloads with the rate-limit code),
so the adapter looped forever.

Changes:
- direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py: bound the 54/55 retry
  branch with the same attempt budget as the server-error branch. Also fixes
  a real production hang for any caller hitting a persistent code 54/55.
  NOTE: vendored file — this fix must also land upstream in
  axisrow/tapi-yandex-direct or it will be lost on the next update_vendor.sh.
- tests/test_read_cassettes.py: +6 v4 success cases in READ_CASES
  (v4goals/v4tags/v4forecast/v4wordstat/v4events/v4finance get-clients-units)
  +1 dedicated check-payment case asserting the deterministic error_code=370
  (non-zero exit). Docstring corrected to drop the false "vcrpy cannot
  intercept v4" claim.
- tests/conftest.py: scrub the login from the v4 request body too
  (GetClientsUnits echoes the account login as a Logins param).
- 7 new cassettes recorded against --sandbox, all secrets/PII redacted
  (token in body, login in request param + response Login field, auth
  headers). Offline replay verified (39 passed, no token/network).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@axisrow

axisrow commented May 29, 2026

Copy link
Copy Markdown
Owner Author

@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality or cause real harm in production.

@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

Claude encountered an error after 3m 27s —— View job


Reviewing PR #457...

  • Read the diff against origin/main
  • Review the retry-loop fix in adapter.py
  • Review conftest.py scrubbing changes
  • Review test_read_cassettes.py changes
  • Check cassettes for leaked secrets/PII
  • Post review

@axisrow

axisrow commented May 29, 2026

Copy link
Copy Markdown
Owner Author

@claude review. The previous review run errored out before posting (empty ANTHROPIC_API_KEY + an internal action error), so this is a re-request. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming, or formatting.

@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 2m 8s —— View job


Review: PR #457 — v4 Live read cassettes + bounded retry-loop

  • Read the diff against origin/main
  • Review the retry-loop fix in adapter.py
  • Review conftest.py scrubbing changes
  • Review test_read_cassettes.py changes
  • Check cassettes for leaked secrets/PII
  • Post review

Verdict: LGTM. No bugs, security issues, or data-loss risks found. The diagnosis (unbounded retry, not a vcrpy×v4 incompatibility) is correct and the fix is minimal and well-targeted.

Correctness — retry fix (adapter.py:178-187)

The change bounds the code in (54, 55) branch with repeat_number < retries_if_server_error, identical to the existing server-error branch immediately below it. Since that branch is pre-existing and proven to terminate, the new branch inherits the same termination guarantee — no off-by-one or new infinite-loop risk. The explicit return False after the budget is exhausted correctly falls through to error_handling, which raises V4LiveRequestsLimitError. Good.

Security / PII — cassettes ✅

Verified all 7 cassettes plus the conftest scrubbers:

  • OAuth token in the v4 JSON body → "token":"<REDACTED>" (_before_record_request, conftest.py:134-142).
  • Account login echoed in the GetClientsUnits Logins param → "param":["REDACTED"] (conftest.py:145-153) — this is the new body-scrub path, and it works.
  • authorization / client-login headers → REDACTED; Set-CookieREDACTED; X-Accel-Info reqid zeroed; response "Login" field → REDACTED.
  • Grep for token|Bearer minus REDACTED across the cassette dir returns nothing. All clean.

The _REAL_LOGIN or _REDACTED trick at test_read_cassettes.py:200 is sound: match_on excludes headers and before_record_request scrubs the login on both record and replay sides, so the body matches whether or not a replaying contributor has an env login set.

Non-blocking note (no change required)

Code 54 is overloaded by v4 for both rate-limit and "Not enough rights". With this fix, a permanent permission error now burns 5 × sleep(10) ≈ 50s before failing (exit 1). That's strictly better than the previous infinite hang, and v4 gives no way to disambiguate the two meanings at the protocol level, so bounded retry is the pragmatic call. Callers who want to skip it entirely can pass retry_if_exceeded_limit=False. Worth a mention only so the ~50s tail isn't surprising in production logs.

Follow-up (already flagged by author)

adapter.py is vendored — the fix must also land in axisrow/tapi-yandex-direct or it'll be reverted on the next update_vendor.sh. The PR body already tracks this.

Note: I couldn't execute pytest in this run (command needs approval), so I relied on static review + the author's reported 39 passed offline. The cassettes and matching logic check out by inspection.

@axisrow axisrow merged commit 91982ce into main May 29, 2026
6 checks passed
@axisrow axisrow deleted the fix/v4-read-cassettes-454 branch May 29, 2026 08:00
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.

v4 read cassettes: vcrpy cannot intercept the vendored v4 Live HTTP client in record mode

1 participant