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. Create a patch function

Copy the template below and adapt it. A patch function has three parts:

  1. A file-scope guard that restricts the patch to the relevant spec files.
  2. A call to applyPatchOrPrintMsgIf that controls whether the patch runs based on the current spec version.
  3. The regular expression replacement that fixes the content.
sub patchSpec_because_of_SemConv_DockerAPIVersions() {
  return unless
    $ARGV =~ m|^tmp/semconv/docs/|
    &&
    applyPatchOrPrintMsgIf('2025-11-21-docker-api-versions',
      'semconv', '1.39.0-dev');

  # 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

  s{
    (https://docs.docker.com/reference/api/engine/version)/v1.(43|51)/(\#tag/)
  }{$1/v1.52/$3}gx;
}

The three arguments to applyPatchOrPrintMsgIf are:

ArgumentDescription
Patch IDA unique identifier (date + short description) printed in log messages.
Spec nameOne of spec, otlp, or semconv.
Target versThe 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.

2. Register the patch call

Add a call to your function inside the section of the main loop that corresponds to the spec kind. For semconv patches, add the call inside the if ($ARGV =~ /^tmp\/semconv/) block:

## Semconv

if ($ARGV =~ /^tmp\/semconv/) {
  # ... existing rewrites ...

  patchSpec_because_of_SemConv_DockerAPIVersions();
}

For OTel spec patches, add the call under the # SPECIFICATION custom processing section. For front-matter patches, add them in the printFrontMatter subroutine.

3. 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

4. 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

5. 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 function and its call from the script. If it is the last remaining patch, you may comment it out instead of deleting, to preserve it as a reference for future patches.