fix(plugin): re-create create_files when a local plugin's source changes#2871
fix(plugin): re-create create_files when a local plugin's source changes#2871mikeland73 wants to merge 2 commits into
Conversation
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
There was a problem hiding this comment.
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 extendsConfigFile.Hash()by incorporatingcreate_filessource-file contents in a deterministic order. - Add a unit test ensuring the hash is stable when inputs are unchanged and changes when a
create_filessource 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.
| if c.Source == nil || len(c.CreateFiles) == 0 { | ||
| return h, nil | ||
| } |
| 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
|
Heads-up on the one red check — This failure is unrelated to this PR. The elixir example uses only the built-in 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 |
Summary
Fixes #2755.
When iterating on a local plugin (
include: ["path:./plugins/..."]), editing one of the files referenced by the plugin'screate_fileshad no effect: the file in.devbox/virtenv/...was never updated, and even deleting it didn't get it re-created on the nextdevbox shell.Root cause
devbox shellshort-circuits inensureStateIsUpToDatewhen the project's state hash is unchanged (File.IsUpToDateAndInstalled→Devbox.ConfigHash→ per-pluginConfig.Hash).For plugins,
Config.Hashresolved to the embeddedconfigfile.ConfigFile.Hash, which only hashes the plugin.json itself. It never looked at the contents of the files referenced bycreate_files. So when a local plugin's source file changed (without any edit todevbox.jsonorplugin.json), the state hash stayed the same, the project was considered up to date, andCreateFilesForConfigwas never called — leaving the stale (or missing) file in place.LocalPlugin.Hashsimilarly only hashes the plugin's path, not its content, so it didn't help either.Fix
Add a
Config.Hashmethod onplugin.Configthat starts fromConfigFile.Hashand additionally folds in the content of eachcreate_filessource file (read via the plugin'sSource.FileContent). The map is walked in sorted order so the hash is deterministic.path:) plugins — source edits now change the hash, invalidate the state, and trigger a re-create of the virtenv files. ✅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?
internal/plugin/plugin_test.go::TestConfigHashIncludesCreateFilesContent, which builds a local pluginConfig, asserts the hash is stable across repeated calls with no change, and asserts it changes when acreate_filessource file's content changes.go test ./internal/plugin/ -run TestConfigHashIncludesCreateFilesContentpasses.go build ./...andgofmt/go vetare clean for the changed package.(The two unrelated
TestGitPluginFileContentCache*failures in this sandbox are pre-existing — theygit commitand the sandbox's commit-signing server rejects it; they don't touch this code path.)cc @tomtaylor (issue reporter)
Generated by Claude Code