readmeta --fix: Repair Broken PyPI README Links10 min read2,048 words

readmeta --fix: Repair Broken PyPI README Links

Learn how readmeta v0.2 --fix repairs broken PyPI README links in built packages, what it can rewrite, and how to add it to a reliable release workflow.

readmeta fixPyPI README linksPython package READMEpackage release workflowPython packaging
readmeta --fix: Repair Broken PyPI README Links

Fix Broken PyPI README Links with readmeta v0.2

A README can look fine on GitHub and still break on PyPI. Relative images and links, references to files inside the repository, raw SVGs, and even broken heading anchors may behave one way in your repo and then lead nowhere on the package page. The annoying part is that a successful build - and a clean twine check - doesn't necessarily catch it.

That's what readmeta is for. It inspects the rendered long_description inside the built package artifact, not just the README you're editing locally. In v0.2, it adds a --fix option that can rewrite many broken relative references in a copy of the built artifact. Your source README stays unchanged, so you can keep repo-friendly relative paths while also shipping a PyPI-friendly version.

This article covers what --fix changes, where it can't help, and how to plug it into a release pipeline. The core idea is simple: build the package, inspect the artifact, fix what can be fixed, and review anything the tool can't safely infer.

Why a README can break on PyPI

Your package README isn't always shown in the same "context" as the files in your repository. On GitHub, a relative reference like docs/shot.png resolves against the repo. PyPI instead displays the package's built long_description, which doesn't automatically share that same repo context. So a link or image that works during development can be dead on the PyPI page.

Common examples include:

  • Relative images, like ![Screenshot](docs/shot.png).
  • Relative links to docs or files elsewhere in the repository.
  • Raw SVG references that don't render correctly once embedded in PyPI.
  • Broken heading anchors, where something like #instalation doesn't match any heading ID in the rendered page.

It's easy to miss these because they aren't Python build errors. The package can build successfully while the README is less useful to people trying it out. twine check helps with package metadata, but (per readmeta's project description) it doesn't flag every broken README reference like this.

The practical takeaway: test what you actually plan to publish. Checking only README.md in a browser isn't the same as checking the description embedded in a wheel or source distribution. That difference matters most when the README drives someone's first impression.

Why not put absolute URLs in the source README?

Absolute links can fix rendering on PyPI, but they add maintenance overhead. A source README should work both in the repo and in the published package. Hard-coding a raw GitHub URL can get brittle - URLs pinned to tags may fail until the tag exists, and URLs pinned to branches can change as the branch moves.

So readmeta keeps relative paths in the source README and rewrites them in the built output. The README stays natural to maintain on GitHub, and the package can use URLs that make sense on PyPI. It's a small separation of concerns, but it helps a lot in practice.

What readmeta --fix changes

The --fix option uses findings from readmeta's package check and tries to rewrite supported relative references in the artifact. It doesn't modify your working-tree README, and it doesn't overwrite the artifact you passed in. By default, it writes a corrected copy next to the input; you can also choose an output location with --out-dir.

For GitHub-based references, the v0.2 default mapping is:

  • Relative images become raw.githubusercontent.com/<repo>/<ref>/<path>.
  • Other relative links become github.com/<repo>/blob/<ref>/<path>.
  • The default ref is main. Use --ref if you need a different branch, tag, or ref.

For example, a README reference to docs/shot.png in the octo/demo repository can become https://raw.githubusercontent.com/octo/demo/main/docs/shot.png. If your assets are hosted somewhere else, --base-url lets you resolve the references against a URL you control (like a CDN).

pip install readmeta

python -m build

readmeta check dist/* --fix --repo octo/demo

The command produces fixed artifacts like *.fixed.whl and *.fixed.tar.gz. Upload those outputs - not the original files - if you want the rewritten descriptions to end up in the release:

twine upload dist/*.fixed.*

Easy to overlook: the original wheel or source distribution remains untouched. If you run --fix and then upload the original files, you effectively bypass the correction. Treat the fixed artifact as a deliberate release candidate and make the upload step target it.

Choosing a repository ref or a custom base URL

The rewritten URL has to point somewhere that will actually exist when someone views the package. The default main ref is convenient, but it's not always right for a versioned release. If your docs or assets must match the exact package release, pass an appropriate value with --ref and make sure that ref exists when you publish.

A custom base URL is useful if you serve stable assets from your own domain or CDN:

readmeta check dist/* --fix \

--repo octo/demo \

--base-url https://cdn.example.com/static/

Pick one approach and stick with it. A repo URL can be simpler to manage; a CDN can be more stable if assets are managed separately. Either way, you still need to verify the resulting URLs are publicly accessible.

A practical release workflow

The least painful way to use --fix is as a post-build release step. Build the wheel and source distribution, let readmeta inspect the built artifacts and rewrite supported references, then review what it changed (and what it couldn't), and only then upload the results. This is different from manually editing generated wheel contents. Manual tweaks are hard to reproduce and tend to vanish the next time someone rebuilds.

A basic local workflow looks like this:

python -m build

readmeta check dist/* --fix --repo octo/demo

# Review any reported issues and the generated *.fixed.* files.

twine upload dist/*.fixed.*

If you use a custom asset host, add --base-url. If your release needs to point at a specific versioned repository ref, add --ref. The "right" values depend on how you publish assets - readmeta can't guess what versioned location you intended to ship.

Make the artifact handoff explicit

A release pipeline should clearly separate inputs from release outputs. With --fix, the original artifacts are inputs and the *.fixed.* files are the upload candidates. If you use --out-dir, the same rule applies: configure later validation and upload steps to use the output directory.

This is worth tightening up in CI, because broad globs can accidentally pick up both original and fixed artifacts. For example, "upload everything under dist/" can mean your correction ran successfully and yet the wrong files still get published. Prefer an upload pattern that clearly selects the outputs you intend to publish. The goal isn't extra ceremony - it's preventing a "we fixed it... then we didn't ship it" moment.

Use the exit code as a gate, not a substitute for review

With --fix, readmeta exits with:

  • 0 when everything is clean or all reported issues were rewritten,
  • 1 when issues remain that can't be rewritten,
  • 2 on an error.

That's useful in CI because it distinguishes a remaining documentation problem from a tool failure. A sensible policy is to stop on any nonzero result, inspect the output, and fix the issue before publishing. If you intentionally handle remaining issues manually, make that a deliberate review step instead of quietly ignoring the exit code.

Also: a "clean rewrite" doesn't prove every URL will land somewhere perfect. It only shows that the tool detected the references and rewrote them according to your configured rules.

Limits, trade-offs, and review points

--fix is intentionally not a general-purpose README editor. It rewrites supported relative image, link, and raw-SVG findings. It avoids rewriting content inside fenced code blocks, inline code, and <pre> / <code> regions. That distinction matters: examples that demonstrate a broken pattern shouldn't suddenly become live links just because the rest of the document got fixed.

Broken in-page anchors are different. A link like #instalation can't be turned into a valid absolute GitHub URL; there's no safe destination to infer when the matching heading doesn't exist. readmeta reports those as remaining issues for manual correction. Fix the typo, add the intended heading, or remove the link - then rebuild and rerun the check.

The boundary between automatic and manual fixes is a good one. Relative paths can often be resolved once you provide a repository or base URL. A broken anchor needs editorial judgment. If the tool "guessed" a destination, the output might look repaired even when it isn't.

What to verify before publishing

Before you upload, check:

  • The build completed and the artifacts you expect are present.
  • readmeta ran against the built artifacts, not just the source README.
  • Your --repo, --ref, or --base-url values point to the intended public locations.
  • Remaining broken anchors and other reported issues were reviewed.
  • Your upload command selects fixed artifacts rather than the originals.
  • The final package page shows the README and links as expected.

For a small README, this review is quick. For packages with lots of screenshots and documentation links, adding the check into release automation is a win: it runs consistently, instead of relying on someone remembering to inspect the PyPI page manually.

Project details

The v0.2 announcement describes readmeta as a standard-library-only tool for Python 3.9 and later, released under the MIT license. You can find it on PyPI and the source on GitHub. Check the project's current documentation for supported options and version details before adopting it in a newer release pipeline.

Conclusion

readmeta v0.2 targets a specific release pain: a README can be valid in the repository but broken in what package users actually see on PyPI. Its --fix option keeps your source files untouched, rewrites supported relative references in generated artifacts, and leaves ambiguous cases like broken heading anchors for you to fix.

The workflow is straightforward: build, check the artifacts, review unresolved findings, and upload the fixed copies. Add it to your release process if PyPI README quality matters for your users, and double-check both the generated URLs and your upload glob before relying on it in CI. Start with the readmeta package on PyPI or review the project source on GitHub.

Frequently Asked Questions

No. It operates on built package artifacts and writes fixed copies, leaving both the original artifacts and your source files untouched. Your repository README can retain relative paths.
In v0.2, it rewrites supported relative image, link, and raw-SVG references. It can resolve them using a repository and ref or a custom base URL.
No. A broken anchor has no reliable destination to infer. readmeta reports it for manual correction and returns exit code 1 when unrewritable issues remain.
Upload the generated fixed artifacts, typically named with a .fixed.whl or .fixed.tar.gz suffix. The original files are not modified.
No. twine check is useful for package metadata checks, while readmeta checks README references in built artifacts and can rewrite supported findings. Use both where appropriate.

Related articles