Skip to content

Pre-release fixes: anchor sdist includes, and stop retrying non-idempotent writes - #30

Merged
igor-ctrl merged 2 commits into
mainfrom
fix/sdist-includes-anchored
Aug 5, 2026
Merged

Pre-release fixes: anchor sdist includes, and stop retrying non-idempotent writes#30
igor-ctrl merged 2 commits into
mainfrom
fix/sdist-includes-anchored

Conversation

@igor-ctrl

Copy link
Copy Markdown
Owner

Overview

Blocks the 0.7.0 PyPI release. The sdist was packaging 1350 files it shouldn't,
including pre-sanitisation copies of docs that still carry real tenant identifiers.

[tool.hatch.build.targets.sdist] listed "src/", "docs/", "tests/" and friends
with no leading slash. Hatchling matches those at any depth, and
.claude/worktrees/agent-*/ holds full copies of this repo — so every nested
checkout's own docs/, src/ and tests/ came along.

Why it was invisible

  • git ls-files | grep <identifiers>clean. The offending files are git-ignored.
  • The wheel → clean. It builds from packages, not the sdist include list.
  • Only unpacking the built sdist showed it: .claude/worktrees/agent-*/docs/configuration.md
    and docs/multi-company.md, from before the commit that replaced real example
    GUIDs with placeholders.

Worth remembering as a general point: for a package that ships an sdist, the source
tree being clean is not the same as the artifact being clean.

Changes

Anchored every pattern with a leading slash, plus a comment explaining why and the
one-liner to re-verify after touching the list.

Test plan

  • uv build; sdist identifier matches 35 → 0, wheel 0
  • sdist size 2 MiB → 434 KiB; contents now exactly src tests packs docs examples + README/LICENSE/NOTICE/CHANGELOG/pyproject
  • uv run pytest1075 passed, 5 skipped
  • Tag v0.7.0 after this merges

Follow-up, not in this PR

.claude/worktrees/ holds 1350 files of stale agent scratch from earlier sessions.
Harmless once the sdist ignores it, but worth clearing out.

[tool.hatch.build.targets.sdist] listed "src/", "docs/", "tests/" and friends
without a leading slash. Hatchling matches those at any depth, and
.claude/worktrees/agent-*/ contains full copies of this repo, so each nested
checkout's own docs/, src/ and tests/ were swept in: 1350 extra files, 2 MiB
instead of 434 KiB.

Some of those copies predate the commit that replaced real example identifiers
with placeholders, so the sdist carried tenant values that no longer exist
anywhere in the tracked tree. The wheel was unaffected — it builds from
`packages` — and `git ls-files` came back clean, so this was invisible to a
source audit and only showed up when the built artifact itself was inspected.

Anchored every pattern with a leading slash. Verified: sdist identifier matches
35 -> 0, wheel 0, contents now exactly src/tests/packs/docs/examples plus the
intended root files.
…otency key

The retry loop keyed only on status: `if status in _RETRYABLE and attempt <
max_retries`, with no notion of whether repeating the request was safe. Network
errors were treated the same way. So a 429/503/504 or a dropped connection on a
POST, PATCH or DELETE re-sent a request the server may already have applied.

That is not hypothetical for this API. Bound actions such as the LLP-utilisation
recalculation take no arguments and mutate on every invocation, so there is no
harmless repeat — one gateway 503 could recalculate twice and nothing in the log
would say so. The header plumbing for Idempotency-Key already existed and simply
was never a precondition for retrying.

Retry now requires either a read-only method or a supplied Idempotency-Key, which
is what allows a gateway or a future server-side implementation to collapse the
duplicate. Otherwise the error surfaces: a visible transient failure the caller
can retry deliberately beats an invisible double-write.

DELETE and PUT are deliberately excluded despite being idempotent by HTTP
semantics — a repeat there surfaces as a 404 or overwrites a concurrent change,
and that is not a call an automatic retry should make for the caller.

Behaviour change, called out in the changelog. 23 tests; no existing test
depended on a mutation being retried.
@igor-ctrl igor-ctrl changed the title Anchor sdist include patterns — nested checkouts were being packaged Pre-release fixes: anchor sdist includes, and stop retrying non-idempotent writes Aug 4, 2026
@igor-ctrl

Copy link
Copy Markdown
Owner Author

Second fix added to this PR

Both changes here are things that must be true before 0.7.0 publishes, so they're
landing together.

Non-idempotent requests were being auto-retried

Found by an independent Codex security review of the downstream MCP server, then
confirmed here. The retry loop keyed only on status — if status in _RETRYABLE and attempt < max_retries — with no notion of whether repeating the request was safe.
Network errors took the same path. So a 429/503/504 or a dropped connection on a
POST/PATCH/DELETE re-sent a request the server may already have applied.

This is not hypothetical for this API: bound actions like the LLP-utilisation
recalculation take no arguments and mutate on every invocation, so a repeat is
never harmless. One gateway 503 could recalculate twice with nothing in the log to
say so. It affects the CLI and every laptop install today, not just the new server.

The Idempotency-Key header plumbing already existed and simply was never a
precondition for retrying.

Now: retry requires a read-only method or a supplied Idempotency-Key.
Otherwise the error surfaces.

Behaviour change, deliberately. A transient 503 on a write that previously
succeeded after a silent retry will now raise. idempotency_key= opts back in.
DELETE and PUT are excluded despite being idempotent by HTTP semantics — a repeat
there 404s or clobbers a concurrent change, which isn't a call an automatic retry
should make on the caller's behalf.

  • uv run pytest1098 passed, 5 skipped (+23)
  • ruff clean
  • No existing test depended on a mutation being retried

@igor-ctrl
igor-ctrl merged commit c564308 into main Aug 5, 2026
3 checks passed
@igor-ctrl
igor-ctrl deleted the fix/sdist-includes-anchored branch August 5, 2026 12:49
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.

1 participant