Skip to content

Key gem caches on each workspace's plugins - #260

Open
apiology wants to merge 7 commits into
load-plugins-when-caching-gemsfrom
key-gem-cache-on-workspace-plugins
Open

apiology wants to merge 7 commits into
load-plugins-when-caching-gemsfrom
key-gem-cache-on-workspace-plugins

Conversation

@apiology

Copy link
Copy Markdown
Owner

Claude: This PR was written by Claude Code on behalf of @apiology.

Problem

A workspace with plugins moves every other workspace to its gem cache:

Solargraph::Workspace.new('/work/rails_app')    # .solargraph.yml: plugins: [solargraph-rails]
plain = Solargraph::Workspace.new('/work/gem')  # no plugins
api_map = Solargraph::ApiMap.new
api_map.catalog Solargraph::Bench.new(workspace: plain)
# expected .../gems, got .../gems-solargraph-rails-1.2.0
Solargraph::CacheDir.gem_dir

In one language server, the plain workspace finds none of its cached gems and rebuilds them into the plugin directory, which castwide/solargraph#1407 introduced.

Solution

Each Workspace keeps the plugins its own config loaded, and that key is passed explicitly to CacheDir.gem_dir(plugins) through Collection::Gem, External, ApiMap and the cache/uncache commands, so loading a plugin changes no process-wide state.

A plugin loaded by plain require into a workspace without config (for example a plugin spec suite) is no longer part of any key; it reads and writes the plain gems directory.

apiology and others added 7 commits October 22, 2026 11:15
CacheDir.gem_dir was keyed on every plugin loaded so far in the
process. Loading a workspace with plugins in .solargraph.yml switched
the gem cache directory for the whole process, so a second workspace
without plugins in the same language server read and wrote the
plugin-keyed directory: its cached gems looked missing and were
rebuilt there.

Workspace now keeps the plugins its config loaded, and that key is
passed explicitly to every reader and writer of gem pins:
Collection::Gem (cache_file, cached?, load, uncache), External,
ApiMap#external and ApiMap.load_with_cache, and Shell cache/uncache.
CacheDir.gem_dir takes the key as an argument, and the process-wide
plugin registry in CacheDir is removed. The cache subprocess is given
the configured plugins of its workspace with --plugins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
ApiMap#external passes workspace&.directory, which is nil for an
ApiMap that has not cataloged a workspace, and External already
guards that case. Widen the directory tags to String, nil, read the
reader once into a local in the two guards so the nil check narrows
it, and drop the ignore marker at the ApiMap call site.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
Library passed the cache subprocess workspace.config.plugins, read
when caching starts. Workspace#merge reloads the config when a new
file appears, so editing the plugins in .solargraph.yml mid-session
made the subprocess key its gem_dir on a different plugin set than
the parent reads, and the parent would re-cache that gem forever.

Workspace now keeps the plugin require paths it loaded at creation
as plugin_paths, and Library passes those with --plugins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
Undercover flagged External#rbs_collection_config_path, which no spec
called, and the Shell cache loop, which the existing specs never
reached because their gem does not exist. Spec the config lookup with
and without a directory, and check that cache --rebuild loads the gem
under the plugins it required.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
Restore the original directory.nil? || directory.empty? guards in
rbs_collection_config_path and rbs_collection_lockfile instead of
copying the reader into a local just so the checker can narrow it.
The checker does not narrow a repeated attr_reader call after a nil
guard, which castwide#1258 fixes, so mark both lines with
a link to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
Brings in castwide master's castwide#1436 rugged build fix via castwide#1407.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
Two examples read gem pins they never cached, relying on earlier
specs to warm the plugin-less cache dir. In plugin jobs those
writes go to the plugin-keyed dir, so the examples found nothing.
Load and update the unloaded gems first, as castwide#1237
does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
@apiology
apiology marked this pull request as ready for review October 22, 2026 17:53
@apiology apiology changed the title Choose the gem cache directory per workspace Key gem caches on each workspace's plugins Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant