Content module patches

Creating and managing temporary patches for content modules between releases.

Spec pages published on this site (OTel specification, OTLP, semantic conventions, OpAMP) come from upstream repositories managed as git submodules under content-modules/. Because the website pins a specific release of each submodule, the raw Markdown is a snapshot that can only be updated by bumping to a newer release.

When you run npm run cp:spec, cp-pages.sh copies submodule content into tmp/, renames README.md files to _index.md, and then runs adjust-pages.pl over every Markdown file. Hugo mounts tmp/ into the site tree so the processed pages appear under /docs/specs/.

What the script does

Spec Markdown files are written for GitHub rendering: they have no Hugo front matter, their links point to GitHub URLs, and image paths assume the repository layout. The adjust-pages.pl script bridges this gap by applying the following transformations to each file:

TransformationDescription
Front matter injectionExtracts the first # Heading as title, generates linkTitle, and emits Hugo front matter. Supports front matter embedded in <!--- Hugo ... ---> comment blocks.
Version stampingAppends spec version numbers (e.g., 1.54.0) to titles and linkTitles for OTel spec, OTLP, and semconv landing pages.
URL rewritingConverts absolute GitHub URLs for spec repositories into local /docs/specs/... paths so cross-spec links work on the site.
Image path adjustmentRewrites relative image paths so they resolve correctly from the Hugo page location.
Content strippingRemoves <details> blocks and <!-- toc --> sections that are not needed on the site.
Temporary patchesApplies regex-based patches for spec issues that have not yet been fixed in a release (see below).

Spec versions are declared at the top of the script in the %versionsRaw hash and are updated automatically by the version-update workflow.

Patching specs between releases

Fixing a broken link or incorrect content in a spec requires a PR to the upstream repository, a new release, and a submodule bump in this repository. That process can take weeks or months. In the meantime, the broken content causes CI failures — most commonly in the automated otelbot/refcache-refresh PRs that check every external link on the site.

To unblock CI without waiting for an upstream release, you can add a temporary patch to adjust-pages.pl. Patches are regex-based rewrites that run at build time and include built-in version tracking: once the spec advances past the target version, cp:spec prints a warning that the patch is obsolete and can be removed.

1. Add a patch entry

Patches are defined in the @patches array near the top of the script. Each entry is a hash with metadata and an apply subroutine. Append a new entry to the array:

my @patches = (
  # ... existing patches ...
  {
    # For the problematic links, see:
    # https://github.com/open-telemetry/semantic-conventions/issues/3103
    #
    # Replace older Docker API versions with the latest:
    # https://github.com/open-telemetry/semantic-conventions/pull/3093
    id      => '2025-11-21-docker-api-versions',
    module  => 'semconv',
    minVers => '1.39.0-dev',
    maxVers => undef,
    file    => qr|^tmp/semconv/docs/|,
    apply   => sub {
      s{
        (https://docs.docker.com/reference/api/engine/version)/v1.(43|51)/(\#tag/)
      }{$1/v1.52/$3}gx;
    },
  },
);

The fields for each patch entry are:

FieldDescription
idA unique ID (date + short description) printed in log messages.
moduleOne of spec, otlp, or semconv.
minVersThe spec version the patch applies to. The patch runs while the submodule is at this version and becomes obsolete once the spec advances past it.
maxVersOptional upper bound version. Set to undef if not needed. When set, the patch won’t apply if the submodule version exceeds this value.
fileA compiled regular expression matching the file paths the patch should apply to, for example qr|^tmp/semconv/docs/|.
contextOptional. Set to 'frontmatter' for patches that modify front matter. Defaults to 'body' (patches that modify page content).
applyAn anonymous subroutine containing the regular expression substitution. For body patches, it operates on $_. For front-matter patches, it operates on $frontMatterFromFile (via $_ aliasing).

No separate registration step is needed — the applyPatches dispatcher automatically iterates over all entries in @patches during the build.

2. Test the patch

Run the spec copy step and verify the patch was applied:

npm run cp:spec

A successful run shows no errors. You can then search the tmp/ output for the problematic content to confirm it was rewritten. For link-related patches, also run:

npm run fix:refcache  # Prunes stale refcache entries, then checks links
npm test              # Full test run including link checking

3. Commit and push

If your patch was created while fixing a refcache PR (e.g., the otelbot/refcache-refresh branch), commit the changes to adjust-pages.pl together with the updated refcache.json, then force-push with lease:

git add scripts/content-modules/adjust-pages.pl static/refcache.json
git commit -m "Patch adjust-pages.pl and refresh refcache"
git push --force-with-lease

4. Remove obsolete patches

Once a new release of the spec includes the fix, cp:spec prints a warning:

INFO: adjust-pages.pl: patch '<id>' is probably obsolete now that
spec '<name>' is at version '<new>' >= '<target>'; if so, remove the patch

When you see this message, delete the patch entry from the @patches array. If it is the last remaining patch, you may comment it out instead of deleting, to preserve it as a reference for future patches.