Skip to content

fix(plugin): re-create create_files when a local plugin's source changes#2871

Open
mikeland73 wants to merge 2 commits into
mainfrom
claude/focused-goldberg-me79to
Open

fix(plugin): re-create create_files when a local plugin's source changes#2871
mikeland73 wants to merge 2 commits into
mainfrom
claude/focused-goldberg-me79to

Conversation

@mikeland73

Copy link
Copy Markdown
Collaborator

Summary

Fixes #2755.

When iterating on a local plugin (include: ["path:./plugins/..."]), editing one of the files referenced by the plugin's create_files had no effect: the file in .devbox/virtenv/... was never updated, and even deleting it didn't get it re-created on the next devbox shell.

Root cause

devbox shell short-circuits in ensureStateIsUpToDate when the project's state hash is unchanged (File.IsUpToDateAndInstalledDevbox.ConfigHash → per-plugin Config.Hash).

For plugins, Config.Hash resolved to the embedded configfile.ConfigFile.Hash, which only hashes the plugin.json itself. It never looked at the contents of the files referenced by create_files. So when a local plugin's source file changed (without any edit to devbox.json or plugin.json), the state hash stayed the same, the project was considered up to date, and CreateFilesForConfig was never called — leaving the stale (or missing) file in place.

LocalPlugin.Hash similarly only hashes the plugin's path, not its content, so it didn't help either.

Fix

Add a Config.Hash method on plugin.Config that starts from ConfigFile.Hash and additionally folds in the content of each create_files source file (read via the plugin's Source.FileContent). The map is walked in sorted order so the hash is deterministic.

  • Local (path:) plugins — source edits now change the hash, invalidate the state, and trigger a re-create of the virtenv files. ✅
  • Built-in / git / github plugins — their file contents are stable for a given version (embedded FS / cached fetch), so the hash is unchanged and there is no behavior change or spurious recompute.

A missing/unreadable source file is tolerated (the file path is still part of the hash) so hashing never hard-fails shell startup.

How was it tested?

  • Added internal/plugin/plugin_test.go::TestConfigHashIncludesCreateFilesContent, which builds a local plugin Config, asserts the hash is stable across repeated calls with no change, and asserts it changes when a create_files source file's content changes.
  • go test ./internal/plugin/ -run TestConfigHashIncludesCreateFilesContent passes.
  • go build ./... and gofmt/go vet are clean for the changed package.

(The two unrelated TestGitPluginFileContentCache* failures in this sandbox are pre-existing — they git commit and the sandbox's commit-signing server rejects it; they don't touch this code path.)

cc @tomtaylor (issue reporter)


Generated by Claude Code

A plugin's hash only covered its plugin.json, so editing a file referenced
by create_files (e.g. a local plugin's source under active development) left
the project's state hash unchanged. As a result the stale file was never
re-created in the virtenv, even after deleting it.

Override Config.Hash to also incorporate the content of each create_files
source file. For built-in/git/github plugins the content is stable per
version, so there is no behavior change; for local (path:) plugins, changes
to the source now invalidate the state hash and trigger a re-create.

Fixes #2755

https://claude.ai/code/session_01BURbKokeurXFkBYFuCd7k8
Copilot AI review requested due to automatic review settings June 15, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes stale create_files outputs for local (path:) plugins by making the project state hash change when the source files referenced by create_files change, ensuring devbox shell re-runs the create-files step when iterating on a local plugin.

Changes:

  • Add (*plugin.Config).Hash() that extends ConfigFile.Hash() by incorporating create_files source-file contents in a deterministic order.
  • Add a unit test ensuring the hash is stable when inputs are unchanged and changes when a create_files source file changes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
internal/plugin/plugin.go Introduces a new Config.Hash() that folds create_files source-file content into the plugin config hash.
internal/plugin/plugin_test.go Adds a test validating hash stability and sensitivity to create_files source changes for local plugins.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/plugin/plugin.go Outdated
Comment on lines +71 to +73
if c.Source == nil || len(c.CreateFiles) == 0 {
return h, nil
}
Comment thread internal/plugin/plugin.go
Comment on lines +85 to +98
for _, filePath := range filePaths {
buf.WriteString(filePath)
contentPath := c.CreateFiles[filePath]
if contentPath == "" {
continue
}
content, err := c.Source.FileContent(contentPath)
if err != nil {
// A missing or unreadable source file should not hard-fail the
// shell; the path is still part of the hash above.
continue
}
buf.Write(content)
}
Address review feedback on the Config.Hash change:

- Only fold create_files source content into the hash for local (path:)
  plugins. For built-in/git/github plugins the content is stable per
  version, and reading it via Source.FileContent could trigger network
  I/O on the fast "is up to date" path, slowing devbox shell or failing
  offline.
- Length-prefix each field (netstring-style) and fold in a fixed-size
  per-file content hash instead of raw bytes, so the preimage is
  unambiguous and the buffer stays small for large source files.

https://claude.ai/code/session_01BURbKokeurXFkBYFuCd7k8

Copy link
Copy Markdown
Collaborator Author

Heads-up on the one red check — test (not-main, ubuntu-latest, project-tests-only, 2.18.0): the only failing test is TestExamples/development_elixir_elixir_hello_run_test.test. All plugin tests passed, including plugins_local, plugins_v2-local, and plugins_builtin.

This failure is unrelated to this PR. The elixir example uses only the built-in elixir@latest package (no local path: plugin) and its init_hook runs mix deps.get, which fetches hex deps over the network — a known flaky point. This PR's change is a strict no-op for non-local plugins (Config.Hash returns the unchanged ConfigFile.Hash unless the source is a *LocalPlugin), so it cannot affect the elixir example.

I don't have permission to re-run the failed job from here (API returns 403). A maintainer re-running the failed job should turn it green.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Local plugins' files in VirtEnv aren't updated on change

3 participants