G
YouTube TikTok Games
Skip to content

Publish placeholder PyPI packages with a README pointing to Agent docs - #26010

Open
iliakur wants to merge 4 commits into
masterfrom
claim-pypi-placeholder-readme
Open

iliakur wants to merge 4 commits into
masterfrom
claim-pypi-placeholder-readme

Conversation

@iliakur

@iliakur iliakur commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

The claim-pypi-name workflow now publishes placeholder packages with a README and a single module. Both point to the Agent integration-management docs: https://docs.datadoghq.com/agent/guide/integration-management/?tab=linux#install. The module prints the pointer on import. The packages were previously empty wheels.

  • Find: the job now also selects names whose latest PyPI release is 0.0.1 — our old empty placeholders — beside names that don't exist yet. Merging this PR forces the update: the next scheduled run republishes every 0.0.1 placeholder as 0.0.2 with the README and module. 0.0.2 is required because PyPI rejects re-uploading the identical 0.0.1 wheel. The list is capped at 256 packages so the publish matrix stays within the GitHub Actions 256-job limit; the overflow is picked up on the next run. The workflow can also be triggered manually.
  • Build: writes a README.md, adds it via readme in the generated pyproject, and adds <module>/__init__.py printing the same message. only-include replaces bypass-selection so the module ships in the wheel.
  • Publish: verify-metadata is removed (defaults to true) since the metadata is now valid. skip-existing stays true: it only tolerates rejections for distribution files that already exist on PyPI, so it never blocks the 0.0.2 upload — every selected package is at 0.0.1 or missing.

Motivation

Users who pip install an integration name get an empty wheel with no guidance. The placeholders now tell them integrations are installed and managed with the Agent, not pip.

A third-party name whose latest release is also 0.0.1 would be selected and fail to publish since we don't own it; fail-fast is off, so only that matrix leg fails.

Checklist before requesting review

  • I self-reviewed the full diff, and this description matches the final change
  • Tests cover the change, or this description explains why none are needed
  • Automated review comments, including Codex review, were addressed or answered
  • I, the human author, understand this change, can explain it to reviewers, and believe it meets the repository standards

…on docs

Placeholder packages now carry a README and a module that print a pointer to
the Agent integration-management docs. The find job also selects names whose
latest PyPI release is 0.0.1 so the old empty placeholders are republished
at 0.0.2.
@iliakur iliakur added the qa/skip-qa Automatically skip this PR for the next QA label Nov 12, 2026 — with ddtool CLI
@dd-octo-sts dd-octo-sts Bot added dev/testing dev/tooling internal Identify a non-fork PR labels Nov 12, 2026
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

evalya-impact-summary

Evalya CI impact

Impact analysis: 0 selected, 0 skipped (of 0 test tasks)
Publish tasks:   4 (always emitted)
Changed files:   1

Full impact report
Learn more about CI impact filtering

@datadog-prod-us1-5

datadog-prod-us1-5 Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Pipelines

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 3254734 | Docs | Give us feedback!

@iliakur
iliakur marked this pull request as ready for review November 12, 2026 16:35
@iliakur
iliakur requested a review from a team as a code owner November 12, 2026 16:35
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-12T16:37:12.054476Z fb11c1f Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@datadog-prod-us1-5 datadog-prod-us1-5 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: WARN

The bulk upgrade could exceed GitHub Actions’ 256-job matrix limit and block all publishing. There are 268 candidate packages, but their current PyPI eligibility remains unverified.

Open Bits AI session

🤖 Bits Code Review · Commit fb11c1f

Comment thread .github/workflows/claim-pypi-name.yaml Outdated
packages.append(name)

with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
output.write(f'packages={json.dumps(packages)}\n')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Batch the bulk upgrade before expanding the publishing matrix

Including existing 0.0.1 placeholders can push the bulk upgrade above GitHub Actions’ 256-job matrix limit. If at least 257 packages qualify, matrix expansion rejects the entire publishing job before any upload, leaving those packages eligible on subsequent runs. This checkout contains 268 distinct candidate packages; their current PyPI eligibility was not verified. Cap each nightly batch so the migration can make progress.

Suggested change
output.write(f'packages={json.dumps(packages)}\n')
output.write(f'packages={json.dumps(packages[:256])}\n')

React: 👍 useful · 👎 wrong · 😕 unclear · 👀 right, but not acting on it

What each reaction means
  • 👍 Useful: correct, and you're fixing it or will.
  • 👎 Wrong: the finding is incorrect, for example the bug can't happen or the code was misread.
  • 😕 Unclear: you can't tell what it means or why it matters.
  • 👀 Right, not acting: correct, but intended, out of scope for this PR, a known risk, or too minor.

🤖 Bits Code Review · Open Bits AI session

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hehe @AAraKKe another usecase for the dispatcher? ;)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: a dry run of the finder against PyPI selects 265 packages, which would have exceeded the 256-job matrix limit on the first post-merge run. Applied the cap in 3254734 — published packages drop out of the selection, so the remainder is picked up on the next run. Also added workflow_dispatch so the batch can be pushed by hand.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I was proposing to do this through ddev not only to be able to have proper tests but because event bus would allow us to do this easily with better minting if I am not mistaken. This helps with the backfilling which we will need to do, the only problem is that all the old projects don't have trusted publishers enabled. So either we need to manually publish the package or enable trusted publishers everywhere.

The event bus proposal is to allow everything to be automted.

@dd-octo-sts

dd-octo-sts Bot commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Validation Report

All 22 validations passed.

Show details
Validation Description Status
agent-reqs Verify check versions match the Agent requirements file ✅
ci Validate CI configuration and code coverage settings ✅
codeowners Validate every integration has a CODEOWNERS entry ✅
config Validate default configuration files against spec.yaml ✅
dep Verify dependency pins are consistent and Agent-compatible ✅
http Validate integrations use the HTTP wrapper correctly ✅
imports Validate check imports do not use deprecated modules ✅
integration-style Validate check code style conventions ✅
jmx-metrics Validate JMX metrics definition files and config ✅
labeler Validate PR labeler config matches integration directories ✅
legacy-signature Validate no integration uses the legacy Agent check signature ✅
license-headers Validate Python files have proper license headers ✅
licenses Validate third-party license attribution list ✅
metadata Validate metadata.csv metric definitions ✅
models Validate configuration data models match spec.yaml ✅
openmetrics Validate OpenMetrics integrations disable the metric limit ✅
package Validate Python package metadata, naming, and wheel contents ✅
pr-description Validate the pull request checklist and visible description length ✅
qa-label Validate the pull request declares whether it needs QA for the next Agent release ✅
readmes Validate README files have required sections ✅
saved-views Validate saved view JSON file structure and fields ✅
version Validate version consistency between package and changelog ✅

View full run

@datadog-prod-us1-5 datadog-prod-us1-5 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: PASS

More details

The PR publishes 0.0.2 placeholders with a README and import-time guidance directing users to Agent-managed installation. The new commit adds manual dispatch and caps publish batches at 256, addressing the matrix-limit problem documented in the PR discussion. Excess packages are deferred to subsequent runs. No reportable findings remain; the earlier review also had none. Wheel building and live publication remain unverified.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 3254734

@AAraKKe AAraKKe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I built the 0.0.2 wheel locally: twine check passes, the README lands in the metadata and importing the module prints the pointer. The 256 cap and workflow_dispatch also take care of the matrix limit Bits flagged.

What's left is making sure the backfill actually lands on every name we own.

Token-era projects need a one-off before this merges. 265 of our names are at an empty 0.0.1. 260 of them were uploaded before #24792 with the old API token (no provenance on PyPI, e.g. datadog-activemq). Only 5 went through this workflow with Trusted Publishing (e.g. datadog-kai-scheduler). PyPI doesn't expose publisher configuration publicly, so I can't confirm it, but unless someone registered this workflow on those 260 projects, their 0.0.2 upload gets rejected with invalid-publisher every night. Someone with owner access on PyPI has to do one of these:

  • Register this workflow (claim-pypi-name.yaml, environment publish-pypi) as a trusted publisher on each project. There's no API for it (pypi/warehouse#14456 is still open), only the publishing settings form. PyPI also caps registrations at 100 per user or IP per 24 hours (troubleshooting docs), so it takes at least 3 days or several admins. Scripting the form with an owner's logged-in session should work, but it isn't supported.
  • Run the publish command once, locally, with a short-lived user-scoped API token from a project owner. Much less work, but it departs from the direction of #24792 and leaves those projects without a trusted publisher if the placeholder ever needs to change again.

Whoever owns the PyPI org should pick one.

Retired integrations aren't covered. Four names we claimed belong to integrations that are no longer in the repo, so no pyproject.toml selects them and they stay empty. Details inline.

Publishing should move into a ddev command on the event bus, with the workflow just running it in a single job. It removes the matrix limit, keeps one failing package from blocking the rest and gives the retired names a place to live. Details inline.

My Feedback Legend

Here's a quick guide to the prefixes I use in my comments:

praise: no action needed, just celebrate!
note: just a comment/information, no need to take any action.
question: I need clarification or I'm seeking to understand your approach.
nit: A minor, non-blocking issue (e.g., style, typo). Feel free to ignore.
suggestion: I'm proposing an improvement. This is optional but recommended.
request: A change I believe is necessary before this can be merged.

The only blocking comments are request, any other type of comment can be applied at discretion of the developer.

packages.append(name)

with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
output.write(f'packages={json.dumps(packages[:256])}\n')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request: Following up on the dispatcher idea: not the Dispatcher itself, but the event bus it's built on fits well here. Let's move finding, building and publishing into a ddev command and have the workflow just run it in one job.

The cap fixes the first run, but packages that can't publish (see the token-era projects in the summary) stay at 0.0.1 and fill the first 256 slots every night. Anything sorting after them is never selected. Today that's datadog-weblogic through datadog-zscaler-private-access, and a new integration sorting there wouldn't get claimed either. A single job has no matrix limit, and the event bus isolates each package the same way ddev validate all isolates each validation. Something like:

@dataclass
class PlaceholderMessage(BaseMessage):
    """`id` is the PyPI project name."""


class PlaceholderProcessor(SyncProcessor[PlaceholderMessage]):
    def process_message(self, message: PlaceholderMessage) -> None:
        wheel = build_placeholder(message.id)
        upload(wheel, token=mint_pypi_token())


class PlaceholderOrchestrator(EventBusOrchestrator):
    async def on_initialize(self) -> None:
        for name in names_to_publish(self._repo):
            self.submit_message(PlaceholderMessage(id=name))
  publish:
    runs-on: ubuntu-latest
    environment: publish-pypi
    permissions:
      contents: read
      id-token: write
    steps:
    - uses: actions/checkout@...
    - uses: ./.github/actions/setup-ddev
    - run: ddev release placeholders publish

setup-ddev installs ddev from the checkout, so this doesn't wait on a ddev release.

Things the command takes over from gh-action-pypi-publish, based on Warehouse's mint_token:

  • Mint a PyPI token per package from a fresh GitHub OIDC token. Each OIDC token can only be exchanged once, and each exchange turns at most one pending publisher into a project, so every new name needs its own exchange. Minted tokens also expire after 15 minutes.
  • A single job can upload to all of our projects: the minted token is scoped to every project with a matching publisher.
  • Keep running from claim-pypi-name.yaml with environment: publish-pypi. Matching uses the workflow file name and the environment, so uploading from anywhere else means registering a new publisher on every project.
  • Decide on attestations. The action signs them today (datadog-kai-scheduler 0.0.1 has provenance). The command would need to sign with pypi-attestations and pass them to twine upload --attestations, or deliberately stop publishing them for placeholders.
  • Accept a plain API token as well, so the one-off for the token-era projects runs the same code.

import urllib.request

packages = []
for pyproject in sorted(pathlib.Path('.').glob('*/pyproject.toml')):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request: This only sees integrations that are in the repo today. Four names we claimed belong to integrations removed since, and they're still empty 0.0.1 releases:

  • datadog-datadog-csi-driver
  • datadog-oracle
  • datadog-snowflake
  • datadog-tokumx

I found them by checking every */pyproject.toml deleted since May 2024 (git log --diff-filter=D -- '*/pyproject.toml') against PyPI. PyPI has no API to list an organization's projects (pypi/warehouse#18041), so I'd keep a checked-in list of retired names that the command publishes along with the ones from pyproject.toml.

MODULE_NAME=$(echo "${PACKAGE_NAME}" | tr - _)
mkdir -p dist "pkg_placeholder/${MODULE_NAME}"
cat > pkg_placeholder/README.md << EOF
This package is a placeholder. It exists only to reserve the name on PyPI and contains no functional code.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: PEP 541 lists "name squatting (package has no functionality or is empty)" as grounds for removing a project, and this sentence describes the package in pretty much those terms. Since PyPI is already looking at these, I'd describe it as what it is, a protection for users:

Datadog reserves this name to protect users from typosquatting and dependency confusion. It is not a Datadog integration.

Datadog integrations are installed and managed with the Datadog Agent, not with pip:
https://docs.datadoghq.com/agent/guide/integration-management/?tab=linux#install

[project]
name = "${PACKAGE_NAME}"
version = "0.0.1"
version = "0.0.2"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: After this, the empty 0.0.1 stays in every project's history; pip just stops picking it. Do we know whether that's enough for PyPI, or do they expect it gone? As far as I know, deleting or yanking a release can only be done from the web UI, one project at a time, so that would be another one-off worth knowing about up front.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot delete a version. "Yanking" it just tells pip to skip it. Since the releases aren't actually malicious or broken, there's no need to yank them.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev/testing dev/tooling internal Identify a non-fork PR qa/skip-qa Automatically skip this PR for the next QA team/agent-integrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants