ci(publish-runtime): add --verbose to twine upload to surface PyPI 403 reason body #1390

Merged
devops-engineer merged 1 commits from ci/twine-verbose-403-reason-body into main 2026-05-17 02:52:26 +00:00
Member

What

Add --verbose to the twine upload invocation in the Publish to PyPI step of .gitea/workflows/publish-runtime.yml.

Why

This is observability hardening motivated by the internal#469 0.1.1003 publish block. The step ran twine upload without --verbose, so on an HTTP 403 twine printed only the bare status (Forbidden) and discarded PyPI Warehouse's human-readable response body — the body that actually states why the upload was rejected (project-scoped-token mismatch, yanked-name collision, account state, etc.). That blind spot made root-cause diagnosis impossible without performing another real upload to the live package, which is exactly the dangerous probe we must avoid.

With --verbose, twine logs the HTTP request/response metadata and the Warehouse error body directly into CI logs, so a future 403 is diagnosable from the run alone.

Token safety

--verbose does not leak the PyPI token. The credential is passed via --password "$PYPI_TOKEN" and is transmitted only inside the Basic-Auth Authorization request header; twine's verbose output dumps request URLs / response status / the Warehouse response body, not the auth secret. The __token__ username is non-secret; the password value is never echoed by --verbose.

Scope

Minimal, surgical change: a single added --verbose \ line on the existing twine upload command. No other steps, env, working-directory, or behavior touched. No tag / pin / upload performed by this PR.

           python -m twine upload \
+            --verbose \
             --repository pypi \
             --username __token__ \
             --password "$PYPI_TOKEN" \
             dist/*

Refs: internal#469

## What Add `--verbose` to the `twine upload` invocation in the **Publish to PyPI** step of `.gitea/workflows/publish-runtime.yml`. ## Why This is observability hardening motivated by the internal#469 `0.1.1003` publish block. The step ran `twine upload` **without** `--verbose`, so on an HTTP 403 twine printed only the bare status (`Forbidden`) and discarded PyPI Warehouse's human-readable response body — the body that actually states *why* the upload was rejected (project-scoped-token mismatch, yanked-name collision, account state, etc.). That blind spot made root-cause diagnosis impossible without performing another real upload to the live package, which is exactly the dangerous probe we must avoid. With `--verbose`, twine logs the HTTP request/response metadata and the Warehouse error body directly into CI logs, so a future 403 is diagnosable from the run alone. ## Token safety `--verbose` does **not** leak the PyPI token. The credential is passed via `--password "$PYPI_TOKEN"` and is transmitted only inside the Basic-Auth `Authorization` request header; twine's verbose output dumps request URLs / response status / the Warehouse response body, not the auth secret. The `__token__` username is non-secret; the password value is never echoed by `--verbose`. ## Scope Minimal, surgical change: a single added `--verbose \` line on the existing `twine upload` command. No other steps, env, working-directory, or behavior touched. No tag / pin / upload performed by this PR. ```diff python -m twine upload \ + --verbose \ --repository pypi \ --username __token__ \ --password "$PYPI_TOKEN" \ dist/* ``` Refs: internal#469
core-devops added 1 commit 2026-05-17 01:45:46 +00:00
ci(publish-runtime): add --verbose to twine upload to surface PyPI 403 reason body
Some checks are pending
CI / Shellcheck (E2E scripts) (pull_request) Successful in 9s
E2E API Smoke Test / detect-changes (pull_request) Successful in 5s
E2E Chat / detect-changes (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 5s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 1m17s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 5s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 58s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 1m4s
CI / Platform (Go) (pull_request) Successful in 4m55s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 52s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 4s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Successful in 3s
qa-review / approved (pull_request) Successful in 3s
security-review / approved (pull_request) Successful in 3s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 3s
sop-tier-check / tier-check (pull_request) Successful in 3s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 1m1s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1s
E2E Chat / E2E Chat (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 1s
CI / Canvas (Next.js) (pull_request) Successful in 6m9s
CI / Python Lint & Test (pull_request) Successful in 6m39s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / all-required (pull_request) Successful in 6m42s
audit-force-merge / audit (pull_request) Successful in 5s
a01d1d8f86
The Publish to PyPI step ran `twine upload` without --verbose. On an HTTP
403, twine's default output prints only the bare status ("Forbidden") and
discards PyPI Warehouse's human-readable response body, which carries the
actual rejection reason (e.g. project-scoped token mismatch, yanked-name
collision, account state). During the internal#469 0.1.1003 publish block
the missing reason body made root-cause diagnosis impossible without
performing another real upload to the live package.

Adding --verbose makes twine log the HTTP request/response metadata and
the Warehouse error body in CI. It does NOT echo the credential: the
PyPI token is passed via --password and sent only in the Basic-Auth
Authorization header, which twine's verbose output does not dump.

Minimal change: single added flag on the existing twine upload
invocation; no other steps or behavior touched.

Refs: internal#469

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
core-security approved these changes 2026-05-17 02:01:30 +00:00
core-security left a comment
Member

security-review — APPROVE (genuine non-author review; reviewer core-security ≠ author core-devops)

Scope: 1 line added, 0 removed, 1 file (.gitea/workflows/publish-runtime.yml). Verified against git diff origin/main...HEAD.

Security (substantive axis): --verbose does NOT leak the PyPI token — verified, not assumed.

  • twine source review (6.2.0, the version pip install build twine resolves): repository.py logs username: __token__ (non-secret) and the password literally as password: <hidden> — never the value. Credential is set on session.auth and emitted as a Basic-Auth Authorization header by requests at send time; twine logs no request headers anywhere. --verbose only raises the twine logger to INFO; it does NOT enable http.client wire-debug.
  • sanitize_url() strips any user:password from logged URLs. INFO additions are only filename/size, Response from {url}: {status} {reason}, and resp.text (the Warehouse 403 reason body — the intended gain).
  • Empirical: ran real twine upload --verbose with a secret-shaped --password; token string appears nowhere in stdout/stderr/tracebacks (password: <hidden>).
  • Note (non-blocking): pip install build twine is unpinned; the <hidden> redaction + URL sanitization are long-standing pre-6.x twine behavior, so robust across versions.

No secret-scanning / credential-exposure finding. Correctly scoped, fail-safe. APPROVE.

**security-review — APPROVE** (genuine non-author review; reviewer `core-security` ≠ author `core-devops`) Scope: 1 line added, 0 removed, 1 file (`.gitea/workflows/publish-runtime.yml`). Verified against `git diff origin/main...HEAD`. **Security (substantive axis): `--verbose` does NOT leak the PyPI token — verified, not assumed.** - twine source review (6.2.0, the version `pip install build twine` resolves): `repository.py` logs `username: __token__` (non-secret) and the password literally as `password: <hidden>` — never the value. Credential is set on `session.auth` and emitted as a Basic-Auth `Authorization` header by `requests` at send time; twine logs no request headers anywhere. `--verbose` only raises the `twine` logger to INFO; it does NOT enable `http.client` wire-debug. - `sanitize_url()` strips any `user:password` from logged URLs. INFO additions are only filename/size, `Response from {url}: {status} {reason}`, and `resp.text` (the Warehouse 403 reason body — the intended gain). - Empirical: ran real `twine upload --verbose` with a secret-shaped `--password`; token string appears nowhere in stdout/stderr/tracebacks (`password: <hidden>`). - Note (non-blocking): `pip install build twine` is unpinned; the `<hidden>` redaction + URL sanitization are long-standing pre-6.x twine behavior, so robust across versions. No secret-scanning / credential-exposure finding. Correctly scoped, fail-safe. **APPROVE.**
core-qa approved these changes 2026-05-17 02:01:42 +00:00
core-qa left a comment
Member

qa-review — APPROVE (genuine non-author review; reviewer core-qa ≠ author core-devops)

Scope verified against git diff origin/main...HEAD: exactly 1 insertion, 0 deletions, 1 file.

Per-axis:

  • Correctness: --verbose is a valid top-level twine upload flag; placement between python -m twine upload \ and --repository pypi \ is correct; backslash continuations intact. No finding.
  • Architecture/Path: change is in .gitea/workflows/publish-runtime.yml; molecule-core reads .gitea/ only — correct dir. No other step/env/working-directory/behavior changed. No finding.
  • Performance: a few extra log lines per upload; negligible; no retry/rate change. No finding.
  • Readability: self-documenting single flag; PR title/body accurately state intent (surface internal#469-class PyPI 403 reason bodies that were previously a bare "Forbidden"). No finding.
  • Security: cross-checked with security-review — --verbose does not leak the token (twine redacts password as <hidden>, sanitizes URLs, logs no auth headers; verified by source + empirical run). No finding.

Behavior change is purely additive observability, low-risk, correctly scoped. APPROVE.

**qa-review — APPROVE** (genuine non-author review; reviewer `core-qa` ≠ author `core-devops`) Scope verified against `git diff origin/main...HEAD`: exactly 1 insertion, 0 deletions, 1 file. **Per-axis:** - Correctness: `--verbose` is a valid top-level `twine upload` flag; placement between `python -m twine upload \` and `--repository pypi \` is correct; backslash continuations intact. No finding. - Architecture/Path: change is in `.gitea/workflows/publish-runtime.yml`; molecule-core reads `.gitea/` only — correct dir. No other step/env/`working-directory`/behavior changed. No finding. - Performance: a few extra log lines per upload; negligible; no retry/rate change. No finding. - Readability: self-documenting single flag; PR title/body accurately state intent (surface internal#469-class PyPI 403 reason bodies that were previously a bare "Forbidden"). No finding. - Security: cross-checked with security-review — `--verbose` does not leak the token (twine redacts password as `<hidden>`, sanitizes URLs, logs no auth headers; verified by source + empirical run). No finding. Behavior change is purely additive observability, low-risk, correctly scoped. **APPROVE.**
Member

/qa-recheck

Non-author APPROVE from core-qa (review 4301, official, non-stale) now present — re-evaluating qa-review gate.

/qa-recheck Non-author APPROVE from core-qa (review 4301, official, non-stale) now present — re-evaluating qa-review gate.
Member

/security-recheck

Non-author APPROVE from core-security (review 4300, official, non-stale) now present — re-evaluating security-review gate.

/security-recheck Non-author APPROVE from core-security (review 4300, official, non-stale) now present — re-evaluating security-review gate.
Member

[core-security-agent] N/A — non-security-touching (CI-only: publish-runtime.yml adds --verbose to twine upload for better PyPI 403 diagnostics. No code changes, no secrets, no exec.)

[core-security-agent] N/A — non-security-touching (CI-only: publish-runtime.yml adds --verbose to twine upload for better PyPI 403 diagnostics. No code changes, no secrets, no exec.)
core-devops closed this pull request 2026-05-17 02:18:04 +00:00
core-devops reopened this pull request 2026-05-17 02:18:07 +00:00
infra-sre reviewed 2026-05-17 02:42:11 +00:00
infra-sre left a comment
Member

infra-sre review

Correctness: python -m twine upload now passes --verbose. On HTTP 403, twine will print the full PyPI Warehouse error response body (e.g. "Invalid username or token") instead of the bare status phrase.

Observability: Directly addresses internal#469 — the 0.1.1003 publish block. When PyPI rejects the upload, the CI log will now surface the reason instead of hiding it.

Safety: No behavioral change on success. --verbose only expands error output; it does not alter the upload itself.

## infra-sre review **Correctness:** ✅ `python -m twine upload` now passes `--verbose`. On HTTP 403, twine will print the full PyPI Warehouse error response body (e.g. "Invalid username or token") instead of the bare status phrase. **Observability:** ✅ Directly addresses internal#469 — the `0.1.1003` publish block. When PyPI rejects the upload, the CI log will now surface the reason instead of hiding it. **Safety:** ✅ No behavioral change on success. `--verbose` only expands error output; it does not alter the upload itself.
devops-engineer merged commit c3cfbea750 into main 2026-05-17 02:52:26 +00:00
Sign in to join this conversation.
No description provided.