How Far to Chase a VS Code Extension That Open VSX Doesn't Carry
Why your settings come across to Antigravity but your extensions don't, how to reconcile the gap with a script, and how to decide between a substitute, a pinned VSIX, moving the job outside the editor, or swapping the registry.
I moved the maintenance environment for a client site over to Antigravity one evening. The first-run setup pulled my VS Code settings across, so I assumed the migration was done. What stopped me was saving a file the way I always do. No formatting ran. I opened the extensions view, and the things I was certain were installed simply weren't there.
The settings had come across. The extensions had not — two different things hiding under the same word, "import."
Here's what I checked that night, and how I decided what to do about the gap. My list held 34 extensions. Five of them, roughly 15%, had no counterpart on Open VSX. That's a small number, and it still took me two evenings to settle.
The settings came across; extensions were always on a different path
Antigravity is built from the VS Code codebase, so the layout and the keybindings feel familiar, and first-run setup can carry over your VS Code or Cursor configuration. That part matched what I expected.
Extensions, though, come from somewhere else entirely. Microsoft's Visual Studio Marketplace is offered for Microsoft's own products and isn't available to derived editors. Antigravity reads from Open VSX, the registry run by the Eclipse Foundation.
So what happens during migration isn't "the extensions disappeared." It's "the editor is looking at a different shelf." Different shelf, different books, different editions. Miss that, and you spend the evening reinstalling the same thing repeatedly. I did exactly that for the first half hour.
Some IDs sit on both shelves. Some sit on only one. And the awkward third case: the ID matches, but the thing behind it came from somewhere else. That third case is what I overlooked first.
Hand the comparison to a script, not your eyes
Scrolling the extensions view and noting what's absent falls apart somewhere past twenty entries. I dumped the ID list from the old environment instead, and queried the Open VSX public API for each one.
Open VSX returns extension metadata as JSON at https://open-vsx.org/api/{publisher}/{name}, and a 404 when nothing is there. That's enough to decide.
#!/usr/bin/env python3"""reconcile_extensions.py — reconcile a local extension list against Open VSX. prepare: run code --list-extensions > installed.txt in the old environment run: python3 reconcile_extensions.py installed.txt > ledger.tsvOutput is TSV. Rows where state is "absent" are the ones you have to decide about."""import jsonimport sysimport timeimport urllib.errorimport urllib.requestAPI = "https://open-vsx.org/api/{ns}/{name}"UA = {"User-Agent": "ext-reconcile/1.0 (personal migration audit)"}def lookup(ext_id): ns, _, name = ext_id.partition(".") if not name: # drop blank lines and comments that aren't in publisher.name form return {"state": "invalid", "note": "not in publisher.name form"} req = urllib.request.Request(API.format(ns=ns, name=name), headers=UA) try: with urllib.request.urlopen(req, timeout=10) as res: data = json.load(res) except urllib.error.HTTPError as e: if e.code == 404: return {"state": "absent", "note": "no matching ID on Open VSX"} if e.code == 429: # throttled: wait once, then retry a single time time.sleep(5) return lookup(ext_id) return {"state": "error", "note": "HTTP {}".format(e.code)} except urllib.error.URLError as e: return {"state": "error", "note": str(e.reason)} published_by = (data.get("publishedBy") or {}).get("loginName", "") access = data.get("namespaceAccess", "") # an unclaimed namespace means someone other than the owner could have published here state = "present" if access == "restricted" else "present-unclaimed" return { "state": state, "version": data.get("version", ""), "published_by": published_by, "access": access, "note": "", }def main(path): print("\t".join(["ext_id", "state", "version", "published_by", "access", "note"])) for line in open(path, encoding="utf-8"): ext_id = line.strip() if not ext_id or ext_id.startswith("#"): continue r = lookup(ext_id) print("\t".join([ ext_id, r.get("state", ""), r.get("version", ""), r.get("published_by", ""), r.get("access", ""), r.get("note", ""), ])) time.sleep(0.4) # keep the public API request rate politeif __name__ == "__main__": main(sys.argv[1] if len(sys.argv) > 1 else "installed.txt")
Expected output looks like this:
ext_id state version published_by access noteesbenp.prettier-vscode present 11.0.0 open-vsx-bot restricteddbaeumer.vscode-eslint present 3.0.10 microsoft restrictedms-azuretools.vscode-docker absent no matching ID on Open VSX
The time.sleep call is there so you don't throw dozens of requests at a public API in a few seconds. Thirty extensions finish in well under a minute, and the TSV becomes the basis for everything that follows.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You'll be able to reconcile your extension list against Open VSX automatically and sort what's missing into 3 buckets, with the publisher verified
✦You'll be able to pick between a substitute extension, a pinned VSIX, moving the job outside the editor, or swapping the registry, based on what your repo actually needs
✦You'll know what to check before installing a third-party republish that merely shares the same ID, so you don't reinstall across every machine later
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
A matching name doesn't guarantee a matching extension
This is the part I hadn't pictured in advance.
An Open VSX namespace stays open until its owner claims it. A matching extension ID doesn't mean the original author published it. Sometimes a third party republished it in good faith. Sometimes the copy stopped being updated years ago.
Of the 29 extensions that existed on my list, three had been published under a name other than the original author's. Nothing misbehaved, and I could have kept using them without noticing. But for an environment I maintain on a client's behalf, I'd rather not wave something through without knowing where it came from.
That's why the script prints the access column. restricted means the namespace ownership has been claimed; an empty value or public means anyone could publish into it. I look at three things before installing:
Does published_by match the publisher on the upstream repository?
Is version close to what the VS Code side was running?
Does the repository linked from the extension page belong to the original author?
Checking all three is tedious, and you only do it at migration time. Ten minutes before installing costs less than half a day after. Wave it through on a name match, and the day something misbehaves you'll suspect your own configuration first, and take the long way around to the real cause.
Four ways to fill the gap, in a fixed order
For the five extensions that were missing, here were the options I lined up:
Approach
When it fits
Cost you take on later
Swap in a substitute
Something on Open VSX already does the job; only setting keys need remapping
Almost none — a diff in your settings file
Install a VSIX by hand
No substitute, upstream publishes a VSIX, and you want the version pinned
Updates become manual; you maintain a ledger and a file store
Move the job outside the editor
The work also holds up as a CLI step or a pre-commit hook
One-time wiring, then less upkeep than before
Swap the registry
Several extensions resist the first three, and the capability is central to the work
The update path changes; reproducing it on another machine takes extra steps
I work down that list from the top, because each step further down clears the immediate problem faster and leaves more for me to carry afterwards. When a case feels borderline, I'd recommend asking whether the row above it can be made to work first.
Three of the five were covered by substitutes. One I installed as a VSIX. The last — a container-management extension — I moved outside the editor entirely. What I had actually been doing with it was tailing logs and restarting things, and a second terminal tab covers both.
That "move it out" decision only surfaces during a migration. On an ordinary day you don't question what's already installed, which makes the inventory itself worth something.
If you install a VSIX by hand, write the version down
The problem is that the line leaves no trace. Months later, standing up another machine, you won't remember what you installed at which version. Since specific versions aren't always available on the registry side, you also need to keep the VSIX file itself.
I keep one TSV that the reinstall runs from.
#!/usr/bin/env bash# vsix_apply.sh — reinstall exactly the versions recorded in the ledger.# usage: ./vsix_apply.sh extensions_ledger.tsv ./vsixset -euo pipefailLEDGER="${1:-extensions_ledger.tsv}"VSIX_DIR="${2:-./vsix}"missing=0while IFS=$'\t' read -r ext_id source version note; do [ "$ext_id" = "ext_id" ] && continue # skip the header row [ "$source" = "vsix" ] || continue # registry-managed entries aren't handled here file="${VSIX_DIR}/${ext_id}-${version}.vsix" if [ ! -f "$file" ]; then echo "missing: ${file} (${note})" >&2 missing=$((missing + 1)) continue fi antigravity --install-extension "$file" --force echo "installed: ${ext_id} ${version}"done < "$LEDGER"if [ "$missing" -gt 0 ]; then echo "${missing} VSIX file(s) are not in the store. Fetch them from upstream again." >&2 exit 1fi
The ledger itself is this small:
ext_id source version noteacme.sample-tools vsix 2.4.1 not on Open VSX; pulled from upstream Releasesesbenp.prettier-vscode registry 11.0.0 tracks updates on Open VSX
--force is there so a reinstall overwrites what's already present; without it, an environment that has something installed is skipped silently — no error, nothing in the output. In a production environment that silence means an old version keeps running while you believe you replaced it. That was the pitfall that cost me the most time, and one flag avoids it. The exit code exists so a missing VSIX can't leave you believing the reinstall completed.
You can point the editor at a different registry from settings. When I first learned that, I assumed it would clear everything at once. It cleared exactly one thing.
Swapping the registry changes more than where the editor looks. It changes the update path. What arrives when an extension updates, and whether that path is under your control, is the first thing to establish.
Looking back, what I wanted wasn't a different shelf at all — it was something that would finish the migration in one move.
It's also a per-machine setting. Another machine, or another person joining, means walking the same steps again. Unlike a VSIX you can record in a ledger, a registry swap moves the assumptions of the environment itself, so it needs its own place to be written down.
And whether upstream's terms anticipate use in a derived editor varies by extension. For an environment I look after for a client, that isn't a question I can skip.
I landed on keeping the swap in reserve. Once four of the five were covered by substitutes and a VSIX, there was no reason left to move the environment's assumptions for the sake of one.
If you're inside a corporate network where connectivity itself needs designing first, Running Antigravity Behind a Proxy matters more than any of this.
Decide in advance what you won't chase
The time that disappears during a migration goes to extensions you keep trying to replace. I stop chasing one when any of these is true:
The last update is more than two years old and the upstream repository is quiet
The job it did can be rewritten as a CLI step or a pre-commit hook
After a week without it, the number of times I actually stalled fits on one hand
The third surprised me. Of the extensions I was sure I needed, exactly one made me stall. The rest I'd been using because they were installed, not because they were required.
A migration isn't about carrying things over; it's about deciding what not to carry. That line is the one I hold even on the days I'm in a hurry.
If there's one thing worth doing today, it's dumping code --list-extensions to a file before you migrate at all. Afterwards, the original list is gone. I forgot to capture mine and spent two hours working from memory.
Thank you for reading. I hope your second evening is shorter than mine was.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.