Claude.app minified source inspection

Medium
Unlisted
look into ~/Applications/Claude.app (it's an app bundle; appears to be electron). is there minifed source code available in that bundle, which we could unminify/inspect?

Yes. I found the installed app at /Applications/Claude.app rather than ~/Applications/Claude.app.

What’s in the bundle

It is an Electron app:

  • Bundle ID: com.anthropic.claudefordesktop
  • Version: 1.14271.0
  • Main archive: /Applications/Claude.app/Contents/Resources/app.asar
  • Web app assets: /Applications/Claude.app/Contents/Resources/ion-dist

Is minified source available?

Yes, there is inspectable bundled JavaScript:

  1. Electron desktop app code inside app.asar

    • app.asar contains Vite-built JS under:
      • .vite/build/index.js
      • .vite/build/index.pre.js
      • .vite/build/mainWindow.js
      • .vite/build/mainView.js
      • .vite/build/quickWindow.js
      • renderer HTML/JS under .vite/renderer/...
    • I extracted it briefly to /tmp and confirmed the JS is present and minified/bundled.
    • The archive also contains package.json, native modules, and some normal npm dependency source like ws and node-pty.
  2. Claude web UI assets outside the ASAR

    • /Applications/Claude.app/Contents/Resources/ion-dist/assets/v1
    • I counted 723 JS files, totaling about 50 MB.
    • Main entry appears to be:
      • /Applications/Claude.app/Contents/Resources/ion-dist/assets/v1/index-CD05FcCU.js
    • Largest chunk is ~7.1 MB.

Are source maps included?

Mostly no.

  • ion-dist has 0 .map files, and the inspected chunks did not include sourceMappingURL comments.
  • app.asar has 0 .map files.
  • Some .vite/build/*.js files do contain comments like:
//# sourceMappingURL=index.js.map

…but the corresponding .map files are not present in the ASAR. So browser/devtools won’t recover original TypeScript/React source from local sourcemaps.

Bottom line

There is plenty of bundled/minified JavaScript available to inspect and beautify, but not original source with sourcemaps. We can still use tools like prettier, esbuild, terser formatting, or AST search to make specific chunks readable, but symbol names are often mangled and module boundaries are mostly lost.

dig into the source in app.asar. can you find any information about how/when local-agent-mode-sessions/skills-plugin gets overwritten?

Found it: skills-plugin is a managed cache

In app.asar, the relevant code is a minified class logged as [SkillsPlugin]. It defines:

  • MF = "local-agent-mode-sessions"
  • Cxr = "skills-plugin"
  • E3 = "skills"
  • Base dir:
    app.getPath("userData") / local-agent-mode-sessions / skills-plugin
    
  • Per-account/org plugin dir:
    ~/Library/Application Support/Claude/local-agent-mode-sessions/skills-plugin/<orgId>/<accountId>
    

On your machine I confirmed that exists at e.g. the Claude skills-plugin cache.

When it gets overwritten

1. On app/session-manager startup

LocalAgentModeSessionManager.onInitialized(...) calls:

qu.startPeriodicSync()

qu is the singleton new SkillsPlugin.

startPeriodicSync() immediately calls:

this.periodicSync()

So the cache is rewritten shortly after the app initializes with account/org context.

2. Every periodic sync interval

Default interval is:

V1e = 600000 // 10 minutes

It can be overridden by a feature/config value:

skillsSyncIntervalMs

Periodic sync only runs while the window is focused:

if (!windowFocused) skip

3. On window focus after the interval has elapsed

There is a focus handler:

if (Date.now() - lastPollTime >= syncIntervalMs) periodicSync()

So focusing Claude after ~10+ minutes can trigger another overwrite/sync.

4. When the web UI calls LocalAgentModeSessions.syncSkills

The IPC interface exposes:

LocalAgentModeSessions.syncSkills

It calls:

qu.triggerSync()

triggerSync() waits 5 seconds, then runs periodicSync().

What exactly gets overwritten

Built-in Anthropic skills are rewritten every sync

Every sync calls:

ensurePluginStructure(pluginDir)
_writeBuiltInSkillsTo(pluginDir)

For each built-in skill, _writeOneBuiltInSkill() does:

  1. Resolve:

    <pluginDir>/skills/<skill-name>/SKILL.md
    
  2. If the skill directory exists and contains anything other than a single SKILL.md, it deletes the entire skill directory:

    rm(skillDir, { recursive: true, force: true })
    
  3. It then writes a fresh SKILL.md:

    writeFile(skillDir/SKILL.md, generatedFrontmatter + prompt)
    

So edits to built-in cached skills like schedule, consolidate-memory, setup-cowork, etc. will be overwritten on every sync. Extra files in those dirs cause the whole directory to be reset.

Remote/server skills are overwritten when changed or missing

It fetches enabled skills from:

/api/organizations/<org>/skills/list-skills?include_wiggle_skills=true&entrypoint=local-agent

Then downloads contents via:

/api/organizations/<org>/skills/download-dot-skill-file?skill_id=...&entrypoint=local-agent

A remote skill is re-downloaded when:

  • it is not in manifest.json
  • updatedAt differs from the manifest
  • its local SKILL.md is missing

Removed remote skills are deleted from disk.

User-created local skills are protected from sync overwrite

Local skills saved through the app are stored in the same plugin dir but marked:

creatorType: "user"
syncManaged: false

The sync logic explicitly preserves syncManaged === false skills and avoids overwriting remote/built-in conflicts with them.

However, if saveLocalSkill(name, ..., overwrite=true) is called, it directly replaces that skill’s SKILL.md. Other files in the user skill directory are kept.

Important practical conclusion

Treat:

~/Library/Application Support/Claude/local-agent-mode-sessions/skills-plugin/...

as a generated cache, not a stable authoring location.

Manual edits there are likely to disappear:

  • built-in skills: on every sync/start/focus-triggered poll
  • remote skills: whenever server updatedAt changes, SKILL.md is missing, or sync cleans orphans
  • user skills: only via explicit local save/delete/enable actions, unless their manifest entry is damaged/lost

I also found a clearCache() / clearPluginDir() method that would delete the whole active skills-plugin dir, but in the source paths I checked, I didn’t find an obvious normal caller for it.

are those API endpoinst public?

No — they’re not public in the “unauthenticated internet API” sense.

They appear to be Claude web/app backend endpoints on the logged-in Claude origin, scoped to your active org:

GET /api/organizations/<org_uuid>/skills/list-skills?include_wiggle_skills=true&entrypoint=local-agent

GET /api/organizations/<org_uuid>/skills/download-dot-skill-file?skill_id=<skill_id>&entrypoint=local-agent

From the desktop code:

  • It first resolves the active org via local auth/session state.
  • It calls these through the app’s authenticated network layer.
  • The URLs include an organization UUID.
  • The data returned is specifically the user/org’s enabled skills.
  • Download uses a skill_id, also under the org route.

So they’re best understood as private, authenticated, undocumented Claude app APIs. If you call them without the Claude session cookies / app auth context, I’d expect 401, 403, or a redirect/login-style response. If called with a logged-in Claude browser/app session, they may work, but that’s using your authenticated account context, not public access.

Also: since these are internal/undocumented endpoints, they can change or disappear without notice.

check anthropic's documentation; are those endpoints documented anywhere?

No — I found official documentation for Skills, but not for the Claude Desktop/claude.ai internal endpoints we saw in the app bundle.

Documented public API endpoints are under Anthropic’s API host:

https://api.anthropic.com/v1/skills
https://api.anthropic.com/v1/files

The official API overview lists Skills API as:

POST /v1/skills
GET /v1/skills

See Anthropic’s API overview and Using Agent Skills with the API.

The desktop app endpoints we found are different:

/api/organizations/<org_uuid>/skills/list-skills?include_wiggle_skills=true&entrypoint=local-agent

/api/organizations/<org_uuid>/skills/download-dot-skill-file?skill_id=<skill_id>&entrypoint=local-agent

I did not find those documented in Anthropic’s public Skills/API docs. The public docs describe:

  • API skill management via /v1/skills
  • API use via container.skills
  • claude.ai custom skills through Settings / UI
  • Claude Code skills as filesystem-based

The docs also explicitly note that skill surfaces are separate: skills uploaded to claude.ai, the API, and Claude Code do not automatically sync across surfaces. That supports the idea that the Claude Desktop endpoints are internal claude.ai/Desktop sync endpoints, not the public Skills API.

So: documented public API: yes, /v1/skills; the specific /api/organizations/.../skills/... endpoints: apparently undocumented/internal.