Skip to content

Merge develop into infrahub-develop#1161

Open
infrahub-github-bot-app[bot] wants to merge 44 commits into
infrahub-developfrom
develop
Open

Merge develop into infrahub-develop#1161
infrahub-github-bot-app[bot] wants to merge 44 commits into
infrahub-developfrom
develop

Conversation

@infrahub-github-bot-app

@infrahub-github-bot-app infrahub-github-bot-app Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Merging develop into infrahub-develop after merging pull request #1149.


Summary by cubic

Adds transparent retry for HTTP 429 responses across the Python SDK with jittered exponential backoff and Retry-After support, configurable via Config, and raising RateLimitError on exhaustion for both clients. Also switches markdown linting to rumdl and adds get_server_information() (version + deployment ID) shown in infrahubctl info; tightens repo rules for comments.

  • New Features

    • Retry 429s with jittered exponential backoff; honors Retry-After (delta-seconds and HTTP-date). New Config fields: rate_limit_retry_enabled, rate_limit_max_retries, rate_limit_backoff_base, rate_limit_backoff_max. New RateLimitRetryHandler and RateLimitError. Applied across _request, multipart uploads, and streaming init on both clients. Docs and tests added.
    • New get_server_information() on async and sync clients returns a ServerInfo (version, deployment ID); infrahubctl info displays the deployment ID.
    • Repository rules: added .agents/rules/code-comments.md (comment sparingly, no references to ephemeral artifacts, standard # type: ignore[...]), repointed .claude/rules to .agents/rules, and clarified AGENTS.md to edit AGENTS.md instead of CLAUDE.md.
  • Bug Fixes

    • Rewind multipart file bodies on retry to avoid empty/truncated uploads.
    • Guard Retry-After parsing: floor negatives, handle overflow, clamp waits.
    • Cap backoff exponent to prevent float overflow.
    • Ensure streaming cleanup on failure with AsyncExitStack/ExitStack.
    • Always raise RateLimitError on exhaustion, even without an attached httpx request.

Written for commit f290f1d. Summary will update on new commits.

Review in cubic

dependabot Bot and others added 30 commits June 29, 2026 09:04
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](actions/cache@v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Swap markdownlint-cli2 (run via npx) for rumdl, a Rust-based markdown
linter installable from PyPI. This removes the Node.js dependency for
the dedicated markdown-lint check, speeds up linting, and consolidates
the configuration into pyproject.toml under [tool.rumdl].

Closes #1138

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Specify phase for IHS-249. Adds spec.md (user journeys P1-P3 + tune,
FR-001..009, success criteria, edge cases, out-of-scope) and the
requirements quality checklist. Resolves the PRD open question by
chaining the underlying transport error as the RateLimitError cause.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan phase for IHS-249. Adds plan.md, research.md, data-model.md,
contracts/ (Config fields, RateLimitError, RateLimitRetryHandler),
and quickstart.md. Points the agent-context plan reference at the
new plan.

Key design finding: the retry chokepoint is not singular — login
routes through _request, but _request_multipart and _get_streaming
bypass it, so the retry driver is applied at all three send sites
per client to satisfy FR-006.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verdict: PROCEED WITH UPDATES. Records the dual-lens critique and
applies its findings:

- E2/X1 (Must-Address): multipart retry could re-send a consumed
  file body; plan + data-model now require rewinding/re-materializing
  the payload per attempt, plus a regression test.
- P3: build-vs-buy rationale (custom vs tenacity/httpx) in research.
- P4: worst-case cumulative wait documented in plan.
- E4: retry logs constrained to URL/attempt/delay (no secrets).
- E6: multipart full-body-on-retry validation added to quickstart.
- E3: mutation-retry assumption accepted (PRD pre-processing 429).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tasks phase for IHS-249: 21 tasks across setup, foundational retry
machinery (handler, RateLimitError, Config fields, drivers on all
three send sites of both clients incl. the E2/X1 multipart re-read
fix), four user-story validation phases, and polish (FR-006 coverage,
E2 regression test, towncrier fragments, docs/lint gates).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compares spec.md against the Jira IHS-249 PRD. Verdict: ALIGNED.
All FR-001..009, journeys, acceptance criteria, success criteria,
and out-of-scope boundaries carried over faithfully. Only additions
are the authorized open-question resolution (RateLimitError __cause__)
and SC-006 (derived from FR-009). No remediation needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the `lint_rumdl` invoke task to `lint_markdown` so it no longer
names the underlying tool, and remove the `dev/commands` rumdl exclude
since that directory no longer exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verified against client.py that InfrahubClient and InfrahubClientSync
are symmetric: each has three direct send sites (_request,
_request_multipart, _get_streaming) and multipart/streaming bypass
_request on both. Send-site audit confirms exactly six sites total,
no fourth path. Tightened plan source-code section, research R1, and
tasks T009/T010 with concrete per-client method line refs so the
retry driver is wired on both clients (FR-008).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add transparent retry-with-backoff on HTTP 429 across both clients (T001-T010):

- New pure RateLimitRetryHandler (Retry-After parsing, jittered/clamped
  exponential backoff, retry-budget decision) in infrahub_sdk/rate_limit.py
- Four rate_limit_* fields on ConfigBase; new RateLimitError(Error)
- Async/sync _send_with_rate_limit_retry drivers wired into _request,
  _request_multipart (with per-attempt file rewind, critique E2/X1), and
  _get_streaming (retry on stream initiation)
- Handler unit tests; client-level test skeleton

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add async/sync parametrized tests exercising the real _request ->
_send_with_rate_limit_retry path: a scripted [429, 200] sequence retries
transparently and returns the 200 after exactly two sends, and non-429
responses pass through untouched with a single send. Driver sleep is
patched via monkeypatch to avoid real waits. Covers T011 and T012 (SC-001).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cover honouring Retry-After on 429 across async and sync clients: delta-seconds,
HTTP-date, zero/past-date (~0s), clamp above rate_limit_backoff_max, and malformed
header falling back to computed jittered backoff while still retrying.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add parametrized async+sync tests asserting persistent 429 exhausts the
retry budget: exactly max_retries + 1 sends, one RateLimitError with
url/attempts/retry_after and a chained httpx.HTTPStatusError cause, and one
WARNING log per retry carrying url, attempt number, and delay. Covers T014;
T015 verified — driver synthesizes and chains the terminal error and tracks
last_retry_after with no source change needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add client-level tests for the rate-limit retry disabled path, the
rate_limit_max_retries budget, and explicit async/sync parity on an
identical 429 sequence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add FR-006 all-paths coverage (regular request, multipart upload, streaming
initiation on both async and sync clients) and the E2/X1 regression test that
asserts a retried multipart upload re-sends the full body byte-for-byte
(modulo the random boundary), driving 429->200 at the httpx transport layer
via pytest-httpx.

Add towncrier fragments for the transparent 429 retry feature and regenerate
the Config reference for the four new rate_limit_* fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parse_retry_after now floors negative delta-seconds at 0.0 (preventing a
negative wait that would crash the sync driver's time.sleep while asyncio
tolerated it) and returns None on OverflowError from pathological digit
strings so the caller falls back to computed backoff.

Adds handler unit tests for both cases, driver-level tests proving
exponential backoff growth/clamping and per-instance jitter divergence,
and a direct unit test of _rewind_multipart_files across its files shapes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records the implement + review tail: 6 impl chunks (T001-T021, all
ticked), 3-agent review of a55cbaa..HEAD, and inline fixes for the
high-severity findings (negative/overflow Retry-After crash; unguarded
SC-003 growth + jitter tests; direct multipart-rewind guard test).
62 tests passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chore(ci): replace markdownlint with rumdl for markdown linting
markdown-lint: pad table delimiter rows (MD060 compact style), add
blank lines around headings/tables/lists (MD022/MD032/MD058), and
replace emphasis-as-heading with plain text (MD036) in the generated
spec artifacts under dev/specs/ihs-249-sdk-429-retry/.

vale: add "backoff" to the spelling exception vocabulary so the
generated config.mdx retry docs pass Infrahub.spelling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses code-review findings on the 429 retry driver:

- Exhaustion no longer leaks RuntimeError when the final 429 response has
  no attached request (e.g. a custom requester): the driver now captures
  the cause (HTTPStatusError or none) and always raises RateLimitError,
  removing the previously-unreachable trailing raise.
- Streaming init: the ExitStack/AsyncExitStack is now closed via
  try/finally so a raise during the failed-429 read cannot leak the stream.
- compute_backoff caps the exponent (2 ** min(attempt, 63)) so a very large
  rate_limit_max_retries can no longer overflow float before the clamp.
- next_delay drops a redundant no-op clamp on the jittered branch.
- Adds a regression test: request-less 429 exhaustion raises RateLimitError
  (cause None), async and sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This is speckit working-state (a local pointer to the active feature
directory), not a project artifact; untrack it while keeping the local
copy so speckit tooling still resolves the feature dir.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-point the .claude/rules adapter at the canonical .agents/ source of
truth so it matches the skills and commands convention. The rule content
moves from the dev/ reference layout into .agents/rules/.

Closes #1147

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-vendor the opsmill dev skills under the opsmill-dev- namespace and
add newly published skills (analyzing-bugs, fixing-bugs,
test-driving-bugs, backporting-fixes, merging-branches,
analyzing-dependency-bumps, pruning-residues). Refresh
skills-lock.json entry names and hashes to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…m comments

Addresses PR review feedback:

- Move the retry driver loop out of both clients into
  RateLimitRetryHandler.send / .asend, and construct the handler once per
  client (self._rate_limit_handler) instead of building it per request on
  each of the two code paths.
- Trim verbose/low-value comments and docstrings on the retry code: drop
  the over-explained Retry-After parse comments, docstrings that restated
  the code (jittered_delay), and the redundant no-op-clamp note; tighten
  the class/driver docstrings.
- Tests: rewrite the stale module docstring, drop the unused __all__
  re-export block, strip internal spec identifiers (SC-/FR-/E2/X1) from
  test docstrings per dev/rules/python-testing.md, and trim an over-long
  test docstring.
- Revert the stray CLAUDE.md SPECKIT plan-pointer edit (not part of this PR).

Behaviour unchanged; 64 unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…asne

chore: ignore .specify/feature.json
chore(skills): sync opsmill dev skills to opsmill-dev- namespace
chore(agents): move dev/rules to .agents/rules
Consolidate to one towncrier fragment (1124.added.md) per review feedback;
it already notes RateLimitError is raised when retries are exhausted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat: retry HTTP 429 responses with jittered backoff
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying infrahub-sdk-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: f290f1d
Status: ✅  Deploy successful!
Preview URL: https://f61e79ba.infrahub-sdk-python.pages.dev
Branch Preview URL: https://develop.infrahub-sdk-python.pages.dev

View logs

@infrahub-github-bot-app infrahub-github-bot-app Bot requested a review from a team as a code owner July 10, 2026 16:26
@github-actions github-actions Bot added the type/documentation Improvements or additions to documentation label Jul 10, 2026
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.57592% with 18 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
infrahub_sdk/client.py 84.81% 10 Missing and 2 partials ⚠️
infrahub_sdk/rate_limit.py 94.56% 3 Missing and 2 partials ⚠️
infrahub_sdk/exceptions.py 87.50% 0 Missing and 1 partial ⚠️
@@                 Coverage Diff                  @@
##           infrahub-develop    #1161      +/-   ##
====================================================
+ Coverage             82.35%   82.96%   +0.61%     
====================================================
  Files                   138      139       +1     
  Lines                 12065    12703     +638     
  Branches               1805     1906     +101     
====================================================
+ Hits                   9936    10539     +603     
- Misses                 1573     1597      +24     
- Partials                556      567      +11     
Flag Coverage Δ
integration-tests 41.95% <32.46%> (+0.97%) ⬆️
python-3.10 56.49% <71.20%> (+0.69%) ⬆️
python-3.11 56.48% <71.20%> (+0.66%) ⬆️
python-3.12 56.49% <71.20%> (+0.67%) ⬆️
python-3.13 56.49% <71.20%> (+0.67%) ⬆️
python-3.14 56.49% <71.20%> (+0.69%) ⬆️
python-filler-3.12 22.71% <22.51%> (+0.27%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
infrahub_sdk/config.py 91.41% <100.00%> (+0.21%) ⬆️
infrahub_sdk/ctl/cli_commands.py 72.97% <100.00%> (+0.42%) ⬆️
infrahub_sdk/data.py 77.77% <100.00%> (+4.44%) ⬆️
infrahub_sdk/exceptions.py 89.69% <87.50%> (-0.12%) ⬇️
infrahub_sdk/rate_limit.py 94.56% <94.56%> (ø)
infrahub_sdk/client.py 76.53% <84.81%> (+0.99%) ⬆️

... and 7 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

dgarros added 2 commits July 10, 2026 16:32
…table-urgc0

# Conflicts:
#	docs/docs/python-sdk/reference/config.mdx
@github-actions github-actions Bot added the group/ci Issue related to the CI pipeline label Jul 11, 2026
dgarros and others added 11 commits July 11, 2026 14:33
Add a get_deployment_id() method to the async and sync clients and
surface the value in `infrahubctl info` (both the simple and --detail
views), making the Infrahub deployment identifier easy to retrieve.

Closes #1017

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add .agents/rules/code-comments.md (applies to all files): comment
sparingly, never reference ephemeral artifacts (specs/tasks/tickets/
spec-kit command names) in any file, and use standard # type: ignore.
Remove the stale "never push to GitHub" boundary from AGENTS.md now
that agents run more autonomously.
…nformation

Replace the unreleased get_deployment_id() with get_server_information(),
which fetches version and deployment_id in a single GraphQL query. This
halves the anonymous `infrahubctl info` round-trips (2 -> 1) and reduces the
authenticated case (4 -> 3). Returns a typed ServerInfo pydantic model;
get_version() is kept unchanged for backward compatibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dev/skills/ was a stale git-tracked copy of the spec-kit skills from the
Spec-kit 0.8.1 upgrade (#979). Its consumer, .claude/skills, was re-pointed
to the canonical .agents/skills source of truth (#1091), leaving this copy
orphaned and misleading. Also drop the now-dead rumdl exclude for it.

Closes #1145

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CLAUDE.md is a thin @AGENTS.md router; add a Never-boundary so agents
edit AGENTS.md instead.
chore(skills): remove orphaned dev/skills directory
…e/actions/cache-6

chore(deps): bump actions/cache from 5 to 6
feat(ctl): add deployment_id to the infrahubctl info command
chore(agents): add comment/reference rule, drop no-push guardrail
Merge stable into develop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/ci Issue related to the CI pipeline type/documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant