[{"data":1,"prerenderedAt":495},["ShallowReactive",2],{"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns":3,"\u002Fideas\u002Fwho-built-this":8},["Island",4],{"key":5,"result":6},"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns",{"head":7},{},{"id":9,"title":10,"authors":11,"body":13,"canonicalUrl":481,"canonicalWebsiteName":482,"category":483,"date":484,"description":485,"extension":486,"featured":487,"fullWidthLayout":487,"image":488,"imageAlt":488,"location":488,"meta":489,"metaImage":488,"navigation":490,"path":491,"seo":492,"stem":493,"venue":488,"venueUrl":488,"__hash__":494},"ideas\u002Fideas\u002Fwho-built-this.md","Who Built This?",[12],"andrew",{"type":14,"value":15,"toc":471},"minimark",[16,42,47,91,119,163,172,183,187,190,201,230,234,253,272,279,307,311,334,337,341,387,411,426,430,433,447,460],[17,18,19,20,27,28,32,33,36,37,41],"p",{},"Michael Stapelberg ",[21,22,26],"a",{"href":23,"rel":24},"https:\u002F\u002Fmichael.stapelberg.ch\u002Fposts\u002F2026-04-05-stamp-it-all-programs-must-report-their-version\u002F",[25],"nofollow","wrote last week"," about Go's automatic VCS stamping: since Go 1.18, every binary built from a git checkout embeds the commit hash, timestamp, and dirty flag, queryable with ",[29,30,31],"code",{},"go version -m"," or ",[29,34,35],{},"runtime\u002Fdebug.ReadBuildInfo()"," at runtime. His argument is that every program should do this, so you can always answer \"what version is running in production?\" without guessing. Go is unusual in doing this by default, and the rest of the ",[21,38,40],{"href":39},"\u002Freports\u002Fthe-package-management-landscape","package management landscape"," varies wildly in how it handles this, if it handles it at all.",[43,44,46],"h2",{"id":45},"compiled-languages","Compiled languages",[17,48,49,50,55,56,59,60,63,64,67,68,32,73,78,79,82,83,86,87,90],{},"Rust's Cargo has ",[21,51,54],{"href":52,"rel":53},"https:\u002F\u002Fgithub.com\u002Frust-lang\u002Fcargo\u002Fissues\u002F5629",[25],"an open issue"," proposing that ",[29,57,58],{},"cargo package"," record the git commit hash in published crates, but nothing has been accepted beyond a ",[29,61,62],{},".cargo_vcs_info.json"," file in the packaged crate, so the conventional approach is a ",[29,65,66],{},"build.rs"," script using crates like ",[21,69,72],{"href":70,"rel":71},"https:\u002F\u002Fgithub.com\u002Frustyhorde\u002Fvergen",[25],"vergen",[21,74,77],{"href":75,"rel":76},"https:\u002F\u002Fgithub.com\u002Fbaoyachi\u002Fshadow-rs",[25],"shadow-rs"," to emit ",[29,80,81],{},"cargo:rustc-env"," directives that become compile-time environment variables readable with ",[29,84,85],{},"env!()",". You get the SHA, branch, timestamp, and dirty flag, but you have to opt in, wire it up, and expose it through a ",[29,88,89],{},"--version"," flag or similar, and there's no way to inspect an arbitrary Rust binary externally.",[17,92,93,98,99,102,103,106,107,112,113,118],{},[21,94,97],{"href":95,"rel":96},"https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fsourcelink",[25],"SourceLink",", now built into the .NET SDK, makes .NET the closest to Go's approach. It sets ",[29,100,101],{},"AssemblyInformationalVersion"," to something like ",[29,104,105],{},"1.0.0+60002d50a...",", embedding the full commit SHA alongside the repository URL for debugger source fetching. ",[21,108,111],{"href":109,"rel":110},"https:\u002F\u002Fgithub.com\u002Fadamralph\u002Fminver",[25],"MinVer"," derives the version entirely from git tags with no configuration file, and ",[21,114,117],{"href":115,"rel":116},"https:\u002F\u002Fgithub.com\u002FGitTools\u002FGitVersion",[25],"GitVersion"," computes semver from branch topology. It's opt-in, but the tooling is mature enough that a .NET developer who wants stamping can get it with a single package reference and no build script.",[17,120,121,122,127,128,131,132,135,136,139,140,142,143,146,147,150,151,156,157,162],{},"Java's ecosystem relies on ",[21,123,126],{"href":124,"rel":125},"https:\u002F\u002Fgithub.com\u002Fgit-commit-id\u002Fgit-commit-id-maven-plugin",[25],"git-commit-id-maven-plugin",", which generates a ",[29,129,130],{},"git.properties"," file and can inject metadata into ",[29,133,134],{},"META-INF\u002FMANIFEST.MF",". Spring Boot's actuator ",[29,137,138],{},"\u002Finfo"," endpoint reads ",[29,141,130],{}," automatically, which means a lot of Spring Boot applications in production actually do have VCS info available, even if the developers who configured it don't think of it as \"stamping.\" You can inspect a JAR's manifest with ",[29,144,145],{},"unzip -p foo.jar META-INF\u002FMANIFEST.MF",", and ",[29,148,149],{},"Package.getImplementationVersion()"," reads it at runtime, though without the plugin you get whatever the maintainer put in the POM version field and nothing else. Gradle has equivalents, and sbt needs two plugins (",[21,152,155],{"href":153,"rel":154},"https:\u002F\u002Fgithub.com\u002Fsbt\u002Fsbt-buildinfo",[25],"sbt-buildinfo"," plus ",[21,158,161],{"href":159,"rel":160},"https:\u002F\u002Fgithub.com\u002Fsbt\u002Fsbt-git",[25],"sbt-git",") to get the same result.",[17,164,165,166,171],{},"Swift Package Manager has no stamping mechanism at all, and a third-party ",[21,167,170],{"href":168,"rel":169},"https:\u002F\u002Fgithub.com\u002FDimaRU\u002FPackageBuildInfo",[25],"PackageBuildInfo"," plugin that shells out to git during the build is about all that exists. SwiftPM has a registry protocol (SE-0292, SE-0391) and private registries exist, but there's no public centralized registry and most packages still resolve directly from git repositories, so the VCS metadata is right there at build time. It clones the repo, checks out the tagged commit, and then throws away everything except the source files. Of all the compiled language toolchains, SwiftPM would have the easiest time stamping and yet doesn't.",[17,173,174,175,178,179,182],{},"Bazel's ",[29,176,177],{},"--workspace_status_command"," flag runs a user-provided script that prints key-value pairs. Keys prefixed ",[29,180,181],{},"STABLE_"," invalidate the build cache when they change; others are \"volatile\" and stale values may be used without triggering a rebuild. The mechanism is powerful and built-in, but the documentation is notoriously confusing and the stable-vs-volatile distinction trips people up regularly.",[43,184,186],{"id":185},"interpreted-languages","Interpreted languages",[17,188,189],{},"For interpreted languages, \"stamping\" means something slightly different, since there's no compiled binary to embed data in: can you determine what version or commit an installed package came from at runtime?",[17,191,192,193,196,197,200],{},"Composer's ",[29,194,195],{},"InstalledVersions::getReference('vendor\u002Fpkg')",", available since version 2.0, returns the git commit SHA of every installed PHP package, backed by ",[29,198,199],{},"vendor\u002Fcomposer\u002Finstalled.json",". This works for both source and dist installs because Packagist records the commit SHA that each tag points to in its API metadata, and Composer preserves it through the lock file into runtime. No other interpreted language package manager preserves this much VCS metadata with this little configuration.",[17,202,203,204,207,208,213,214,217,218,221,222,225,226,229],{},"Python's ",[29,205,206],{},"importlib.metadata.version('pkg')"," gives you the version string but no VCS revision unless you use ",[21,209,212],{"href":210,"rel":211},"https:\u002F\u002Fgithub.com\u002Fpypa\u002Fsetuptools-scm",[25],"setuptools-scm"," or similar to bake the commit hash in at build time. PEP 610 specifies a ",[29,215,216],{},"direct_url.json"," for packages installed directly from VCS, which records the commit hash, but anything installed from PyPI lost its git SHA when the sdist or wheel was built. npm, pnpm, and Yarn make ",[29,219,220],{},"package.json"," version available at runtime but nothing more; npm provenance attestations link published packages to specific commits via Sigstore, though that's registry metadata rather than something embedded in the package itself. RubyGems exposes version at runtime through ",[29,223,224],{},"Gem::Specification"," and the gemspec ",[29,227,228],{},"metadata"," hash allows arbitrary keys, but there's no standard field for git SHA and no convention for using one.",[43,231,233],{"id":232},"system-package-managers","System package managers",[17,235,236,237,240,241,244,245,248,249,252],{},"dpkg stores package version (e.g. ",[29,238,239],{},"1.2.3-1",") queryable with ",[29,242,243],{},"dpkg -s",", and a ",[29,246,247],{},"Vcs-Git"," field exists in source package metadata, but that field never propagates to installed binary packages. RPM actually has a dedicated ",[29,250,251],{},"VCS"," tag (tag 5034) that can store the upstream repository URL and potentially a commit SHA, but most Fedora RPMs don't bother setting it.",[17,254,255,256,259,260,263,264,267,268,271],{},"Arch's pacman has a clever approach for VCS packages: packages suffixed ",[29,257,258],{},"-git"," run a ",[29,261,262],{},"pkgver()"," function in the PKGBUILD that encodes the commits-since-last-tag and short hash into the version string itself, like ",[29,265,266],{},"1.0.3.r12.ga1b2c3d",", so the version you see in ",[29,269,270],{},"pacman -Qi"," actually contains the commit info. Regular packages built from release tarballs just carry the upstream version number, though.",[17,273,274,275,278],{},"Homebrew records the formula URL (typically a tarball) and its SHA256, plus a Homebrew-specific ",[29,276,277],{},"revision"," field for rebuilds, but no upstream git commit survives installation. Flatpak and Snap both have version metadata in their app manifests but no VCS revision field in either format.",[17,280,281,282,285,286,289,290,293,294,296,297,300,301,306],{},"Nix is where Stapelberg's post originates, and it's a good illustration of the problem: store paths encode a content hash, not a VCS revision, and fetchers like ",[29,283,284],{},"fetchFromGitHub"," download a tarball with no ",[29,287,288],{},".git"," directory. Even ",[29,291,292],{},"builtins.fetchGit"," strips ",[29,295,288],{}," for reproducibility. The ",[29,298,299],{},".rev"," attribute exists during Nix evaluation but isn't written to the store, so Stapelberg's ",[21,302,305],{"href":303,"rel":304},"https:\u002F\u002Fgithub.com\u002Fstapelberg\u002Fnix",[25],"go-vcs-stamping.nix"," overlay has to bridge that gap for Go specifically, and the underlying problem affects every language built through Nix.",[43,308,310],{"id":309},"container-images","Container images",[17,312,313,314,321,322,325,326,329,330,333],{},"OCI images have their own annotation spec for this: the ",[21,315,318],{"href":316,"rel":317},"https:\u002F\u002Fgithub.com\u002Fopencontainers\u002Fimage-spec\u002Fblob\u002Fmain\u002Fannotations.md",[25],[29,319,320],{},"org.opencontainers.image.revision"," label carries the VCS commit hash, and ",[29,323,324],{},"org.opencontainers.image.source"," points to the repository URL. ",[29,327,328],{},"docker buildx"," can set these automatically from git context, and GitHub Actions' ",[29,331,332],{},"docker\u002Fmetadata-action"," populates them from the workflow environment, so a CI-built image can carry its commit SHA and repo URL without any manual wiring.",[17,335,336],{},"Plenty of Dockerfiles don't set these labels in practice, and even when they're present they describe the image build, not necessarily the application inside it. An image built from a Go binary that was itself built without VCS stamping will have the commit that changed the Dockerfile, which may or may not be the commit that changed the application code, so image-level and application-level stamping end up being two separate problems.",[43,338,340],{"id":339},"source-archives","Source archives",[17,342,343,344,351,352,359,360,363,364,367,368,370,371,373,374,376,377,380,381,383,384,386],{},"Git's own ",[21,345,348],{"href":346,"rel":347},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgit-archive",[25],[29,349,350],{},"git archive"," command supports ",[21,353,356],{"href":354,"rel":355},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgitattributes#_creating_an_archive",[25],[29,357,358],{},"export-subst"," in ",[29,361,362],{},".gitattributes",", expanding placeholders like ",[29,365,366],{},"$Format:%H$"," into the full commit hash, which is the intended mechanism for embedding commit info in archives without ",[29,369,288],{},". GitHub, GitLab, Gitea, and Forgejo all use ",[29,372,350],{}," internally for their downloadable tarballs and zipballs, so ",[29,375,358],{}," works on all of them. If you add ",[29,378,379],{},"version.txt export-subst"," to your ",[29,382,362],{}," and put ",[29,385,366],{}," in that file, the tarball will contain the full commit hash.",[17,388,389,390,393,394,399,400,403,404,407,408,410],{},"The catch is reproducibility. Abbreviated hash placeholders like ",[29,391,392],{},"$Format:%h$"," produce different-length output depending on the number of objects in the repository, and GitHub's servers don't always agree on object counts. The same tarball URL can return different contents at different times, which breaks checksum verification. ",[21,395,398],{"href":396,"rel":397},"https:\u002F\u002Fgithub.com\u002FNixOS\u002Fnixpkgs\u002Fissues\u002F84312",[25],"NixOS\u002Fnixpkgs#84312"," documents this problem in detail. Full hashes (",[29,401,402],{},"%H",") are stable, but ref-dependent placeholders like ",[29,405,406],{},"%d"," change as branches move. The mechanism works, but anyone who checksums tarballs, which is most package managers, has to treat ",[29,409,358],{}," repos as a source of non-determinism.",[17,412,413,414,419,420,422,423,425],{},"The same thing happens with package archives, where the version from the manifest file survives but the commit that produced it doesn't unless the build backend explicitly stamped it in. (An ",[21,415,418],{"href":416,"rel":417},"https:\u002F\u002Fgithub.com\u002Fnpm\u002Fnpm\u002Fissues\u002F20213",[25],"npm bug in 6.9.1"," once accidentally included ",[29,421,288],{}," directories in published tarballs, and it was treated as a serious defect.) A developer tags a commit, CI builds an artifact from that tag, the build process strips ",[29,424,288],{},", and the resulting package carries only the version string.",[43,427,429],{"id":428},"trusted-publishing-and-embedded-stamping","Trusted publishing and embedded stamping",[17,431,432],{},"Trusted publishing through Sigstore addresses this from the registry side. When a package is published from CI with OIDC-based trusted publishing, the registry records which commit, repository, and workflow produced it, with a cryptographic signature in a transparency ledger. npm and PyPI both support this today. The provenance metadata lives at the registry rather than in the artifact, but you can look up an artifact's attestation by its hash, so if you have the artifact you can trace it back to the commit that produced it without the artifact itself needing to carry that information.",[17,434,435,440,441,446],{},[21,436,439],{"href":437,"rel":438},"https:\u002F\u002Fwww.softwareheritage.org\u002F",[25],"Software Heritage"," could eventually enable something similar from the source side. They archive public source code repositories and assign intrinsic identifiers (",[21,442,445],{"href":443,"rel":444},"https:\u002F\u002Fdocs.softwareheritage.org\u002Fdevel\u002Fswh-model\u002Fpersistent-identifiers.html",[25],"SWHIDs",") based on content hashes, so in principle you could go the other direction too: given a source tree or file, look up which commits and repositories it appeared in. That archive is already large and growing, though the tooling to make these lookups practical for everyday debugging isn't there yet.",[17,448,449,450,455,456,459],{},"All this research got me thinking about how it could integrate with ",[21,451,454],{"href":452,"rel":453},"https:\u002F\u002Fgithub.com\u002Fgit-pkgs\u002Fgit-pkgs",[25],"git-pkgs",", which already tracks the dependency side of this: who added a package, when it changed, what the version history looks like in your repo. Its ",[29,457,458],{},"browse"," command opens the installed source of a package in your editor, but that's the installed files with no git history.",[17,461,462,463,466,467,470],{},"If packages reliably carried their source commit, there's a more interesting version of that command: clone the upstream repository and check out the exact commit your installed version was built from. You'd get ",[29,464,465],{},"git log",", ",[29,468,469],{},"git blame",", the full context of what changed between the version you have and the version you're upgrading to, all from your local terminal. The stamping metadata is the missing link between \"I depend on this package at this version\" and \"here is the code that produced it, with its history.\"",{"title":472,"searchDepth":473,"depth":473,"links":474},"",2,[475,476,477,478,479,480],{"id":45,"depth":473,"text":46},{"id":185,"depth":473,"text":186},{"id":232,"depth":473,"text":233},{"id":309,"depth":473,"text":310},{"id":339,"depth":473,"text":340},{"id":428,"depth":473,"text":429},"https:\u002F\u002Fnesbitt.io\u002F2026\u002F04\u002F07\u002Fwho-built-this","nesbitt.io","software-supply-chains","2026-04-07","Tracing a dependency back to its source commit.","md",false,null,{},true,"\u002Fideas\u002Fwho-built-this",{"title":10,"description":485},"ideas\u002Fwho-built-this","UibB2PMDFwA8bXZxJ3vbtm9ACGyk88fEOAITGc3elQY",1780596103755]