diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..2db155f
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,51 @@
+---
+name: Build tagged version
+
+on:
+  push:
+    tags:
+      - '*'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Install dependencies
+        run: |
+          sudo apt update
+          sudo apt install -y texlive-latex-extra make
+
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          submodules: true
+          fetch-depth: 0
+
+      - name: Verify Tag is on Main Branch
+        id: verify_tag
+        run: |
+          TAG_COMMIT=$(git rev-list -n 1 ${GITHUB_REF})
+          if git merge-base --is-ancestor $TAG_COMMIT origin/main; then
+            echo "on_main=true" >>"$GITHUB_OUTPUT"
+          else
+            echo "on_main=false" >>"$GITHUB_OUTPUT"
+          fi
+
+      - name: Build
+        id: build_step
+        run: |
+          cd src
+          make
+          TAG_NAME=${GITHUB_REF#refs/tags/}
+          echo "pdfFilename=$GITHUB_WORKSPACE/src/$TAG_NAME.pdf" >> "$GITHUB_OUTPUT"
+          mv -v ts.pdf "$TAG_NAME.pdf" && stat "$TAG_NAME.pdf" && realpath "$TAG_NAME.pdf"
+
+      - name: Release
+        if: >
+          steps.verify_tag.outputs.on_main == 'true' &&
+          steps.build_step.outcome == 'success'
+        uses: ncipollo/release-action@v1
+        with:
+          artifacts: '${{ steps.build_step.outputs.pdfFilename }}'
+          generateReleaseNotes: true
+          artifactContentType: 'application/pdf'
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..b39cb49
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/external/listings"]
+	path = src/external/listings
+	url = git://git.gnu.org.ua/listings.git
diff --git a/src/Makefile b/src/Makefile
index 5de6844..a04d926 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -13,7 +13,7 @@
 
 FIGURES = $(patsubst %.dot,%.pdf,$(wildcard *.dot))
 
-TSPDF = pdflatex ts | grep -v "^Overfull"
+TSPDF = TEXINPUTS=./external/listings//: pdflatex ts.tex | grep -v "^Overfull"
 
 default: rebuild
 
@@ -24,6 +24,8 @@ refresh:
 	$(TSPDF)
 
 rebuild:
+	test -d ../.git && git submodule update --init --recursive
+	make -C ./external/listings
 	$(TSPDF)
 	$(TSPDF)
 	$(TSPDF)
diff --git a/src/external/listings b/src/external/listings
new file mode 160000
index 0000000..2d9771b
--- /dev/null
+++ b/src/external/listings
@@ -0,0 +1 @@
+Subproject commit 2d9771b13d4ce189cb8d9cb0651bc688a86391ba
diff --git a/src/macros.tex b/src/macros.tex
index eafb519..cea1a46 100644
--- a/src/macros.tex
+++ b/src/macros.tex
@@ -78,7 +78,9 @@
 % Set the xref label for a clause to be "Clause n", not just "n".
 \makeatletter
 \newcommand{\customlabel}[2]{%
-\@bsphack \begingroup \protected@edef \@currentlabel {\protect \M@TitleReference{#2}{\M@currentTitle}}\MNR@label{#1}\endgroup \@esphack%
+  \@bsphack
+  \protected@write\@auxout{}{\string\newlabel{#1}{{#2}{\thepage}}}%
+  \@esphack
 }
 \makeatother
 \newcommand{\clauselabel}[1]{\customlabel{#1}{Clause \thechapter}}
diff --git a/src/ts.tex b/src/ts.tex
index faff217..1192fa6 100644
--- a/src/ts.tex
+++ b/src/ts.tex
@@ -10,7 +10,7 @@
 \usepackage[iso,american]
            {isodate}      % use iso format for dates
 \usepackage[final]
-           {listings}     % code listings
+           {./external/listings/listings}     % code listings
 \usepackage{longtable}    % auto-breaking tables
 \usepackage{ltcaption}    % fix captions for long tables
 \usepackage{relsize}      % provide relative font size changes