◉ANTIGRAVITY LABJP
Articles/Editor View
⟐ Editor View/2026-09-17Advanced

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.

Open VSXExtensionsVSIXMigration9Antigravity409

✦ Premium Article

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.tsv
 
Output is TSV. Rows where state is "absent" are the ones you have to decide about.
"""
import json
import sys
import time
import urllib.error
import urllib.request
 
API = "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 polite
 
 
if __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	note
esbenp.prettier-vscode	present	11.0.0	open-vsx-bot	restricted
dbaeumer.vscode-eslint	present	3.0.10	microsoft	restricted
ms-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.

or
Unlock all articles with Membership →
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.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⟐ Editor View2026-11-01
Can't Find the Conversation History Page? Three Ways Back to Past Chats After Antigravity 2.28.2
If the Antigravity app no longer shows a conversation history page after an update, here is where to look instead: the search button, the conversation navigator, and a full-text search of the brain folder, plus one habit that makes chats findable later.
⟐ Editor View2026-10-28
Antigravity IDE Freezes After Opening a Folder on Linux + Nvidia: A Triage Order
A Linux + Nvidia report says the IDE stops responding seconds after a folder opens. What the thread already ruled out, and the order I'd try next. Cause unconfirmed.
⟐ Editor View2026-10-21
I Wondered Whether to Sort My Diffs by Machine, So I Ran It on 106 Commits. It Mattered for 8, and for One 1,776-File Change
I wrote a small script that orders a git diff by reading priority and ran it on 106 recent commits. It earned its place in 8 of them and one bulk change. Rules, full code, and the silent misclassification traps.