A repository is not a payload
Four browser extensions had to be turned into downloadable payloads. They live in their own repositories, with the things repositories have: a README, a licence, agent instructions, store artwork, a test directory. None of that belongs in bytes a browser loads.
One of them already had a build.sh saying exactly that, in a comment — ships only runtime files — so the job was mostly making that idea common rather than inventing it. What made it worth writing down is what the shared packer refused.
The refusal#
pack: ~/Sodium/_metadata would be refused by Chrome
names starting with _ are reservedChrome rejects any top-level file or directory whose name starts with an underscore, except _locales. The error, if you get that far, is "Cannot load extension with file or directory name _metadata. Filenames starting with '_' are reserved for use by the system."
_metadata is not source. It is what Chrome writes into a copy it installed itself — a manifest fingerprint and a verified-contents file — and it was sitting in that tree because the tree had come back from an installed profile at some point. Ordinary git status cleanliness would not have flagged it; it is a directory that has been there, harmlessly, for a long time.
Zipped up and shipped, it would have produced an extension that downloads successfully, verifies its checksum successfully, unpacks successfully, and then cannot load. The failure surfaces in a customer's browser, minutes later, with nothing connecting it to the cause.
It is dropped now rather than refused, because refusing asks a human to delete a directory Chrome will simply write again. Any other reserved name still stops the pack, loudly, with the source path in the message.
The check that should have been obvious sooner#
Packing correctly is not the same as being loadable, and there was no reason to guess. The mechanism for loading one was already built — the Hub starts a Chrome over the DevTools pipe and calls Extensions.loadUnpacked — so the packer's output goes through exactly that before anything is published:
chrome: Chrome/152.0.7977.64
load antiblur-1.0.0 -> ikdoijnmddhhkiiipjhiakfcdgjphmnf
load argon-0.1.0 -> amkkhglnoibjoienbahjijmoclmdgaon
load sodium-3.4 -> dodkimmdlaknmmaihamleodckfhlfjmc
load typeswap-1.0.0 -> mmfofikbphjojcplkjdbbmpfkmpblfmk
ALL FOUR LOADSixty lines of throwaway script, run once before a publish that cannot easily be taken back. A payload the browser refuses fails long after the download reported success, and by then it is on somebody else's machine.
A smaller thing, for completeness#
The packer stages with rsync into a directory and zips that, rather than passing a list of exclusions to zip. An exclusion pattern that silently fails to match is a document shipped to a customer. A staging directory can be looked at.