Skip to content

fix(plugin): regenerate local plugin create_files when their content changes#2867

Open
mikeland73 wants to merge 3 commits into
mainfrom
claude/focused-goldberg-3vvl8e
Open

fix(plugin): regenerate local plugin create_files when their content changes#2867
mikeland73 wants to merge 3 commits into
mainfrom
claude/focused-goldberg-3vvl8e

Conversation

@mikeland73

Copy link
Copy Markdown
Collaborator

Summary

Fixes #2755.

When developing a local plugin (included via path:), editing a file referenced by the plugin's create_files does not update the copy in .devbox/virtenv/.... Even deleting the generated file and re-entering the shell leaves it missing/stale, which makes local plugin development confusing.

Root cause

Devbox skips recomputing the environment (and rewriting plugin files) when its state hash is unchanged (internal/lock/statehash.go). That state hash is derived in part from Devbox.ConfigHash(), which hashes each included plugin config via plugin.Config.Hash() — and that resolved to the embedded configfile.ConfigFile.Hash(), which only hashes plugin.json.

The files referenced by create_files are read from local disk separately and are not part of any hash. So editing a local plugin's source file (e.g. plugin/test/test.txt) changes nothing the state hash tracks:

  • devbox.json — unchanged
  • the plugin's plugin.json — unchanged
  • devbox.lock, nix manifest, print-dev-env cache — unchanged

isStateUpToDate therefore returns true, CreateFilesForConfig is never called, and the stale virtenv copy stays in place.

(Note: shouldCreateFile already returns true for files under .devbox/, so once recomputation is triggered the file is rewritten correctly — the gap was purely in cache invalidation.)

Fix

Add a Hash() method on plugin.Config that shadows the embedded ConfigFile.Hash(). For local plugins, it folds the contents of the files referenced by create_files into the hash (sorted for determinism), so editing them invalidates Devbox's cached state and regenerates the virtenv files on the next devbox shell/run.

This is scoped to local plugins on purpose:

  • Built-in plugin files are embedded in the Devbox binary, already covered by the DevboxVersion field in the state hash.
  • Remote (github/git) plugins are addressed by an immutable ref, and reading their content can require network/cache access we don't want on every command.

The only caller of plugin.Config.Hash() is Devbox.ConfigHash(), so the override is tightly scoped to the cache-invalidation path.

How was it tested?

  • go build ./internal/..., go vet ./internal/plugin/, and gofumpt — all clean.
  • New unit tests in internal/plugin/local_test.go:
    • TestLocalPluginHashIncludesCreateFilesContent — hash changes when a create_files source file is edited (plugin.json untouched). Fails before this change, passes after.
    • TestLocalPluginHashStableWithoutContentChange — hash is stable across calls when nothing changes.

Note: the nix-backed end-to-end testscripts could not be executed in the authoring environment (no nix binary), but the unit tests exercise the exact hashing logic that gates regeneration.


cc @tomtaylor (issue reporter) — thanks for the clear repro repo, it made this easy to pin down.


Generated by Claude Code

…changes

Local plugins' files in the virtenv were never recreated after editing the
source files referenced by create_files. Devbox decides whether to recompute
the environment (and rewrite plugin files) by comparing a state hash that is
derived, in part, from each included plugin config's hash. That hash only
covered plugin.json, not the content files that create_files references, so
editing a local plugin's source file left the state hash unchanged and the
stale virtenv copy in place.

Override Config.Hash() to fold the contents of create_files source files into
the hash for local plugins. Built-in plugin files are embedded in the binary
(covered by the Devbox version) and remote plugins are addressed by an
immutable ref, so those are left to the config-file hash alone.

Fixes #2755
Copilot AI review requested due to automatic review settings June 14, 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 cache invalidation for local (path:) plugins so that edits to files referenced by a plugin’s create_files trigger environment regeneration and the .devbox/virtenv/... copies get rewritten, addressing #2755.

Changes:

  • Adds (*plugin.Config).Hash() to incorporate local create_files source file contents into the plugin config hash.
  • Adds unit tests ensuring the local plugin hash changes when a referenced create_files file changes, and remains stable otherwise.

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 Config.Hash() override to include local create_files content in the cache key used for state up-to-date checks.
internal/plugin/local_test.go Adds unit tests validating the new hashing behavior 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 +88 to +99
configHash, err := c.ConfigFile.Hash()
if err != nil {
return "", err
}

local, ok := c.Source.(*LocalPlugin)
if !ok {
return configHash, nil
}

buf := bytes.Buffer{}
buf.WriteString(configHash)
Comment thread internal/plugin/plugin.go Outdated
Comment on lines +110 to +116
for _, contentPath := range contentPaths {
content, err := local.FileContent(contentPath)
if err != nil {
return "", errors.WithStack(err)
}
buf.Write(content)
}
claude added 2 commits June 14, 2026 14:24
…lugins

Address review feedback on the local plugin cache-invalidation fix:

- configfile.ConfigFile.Hash() only covers ConfigFile fields, so the previous
  seed omitted plugin-only fields (create_files destinations, version, readme,
  __remove_trigger_package). Fold these into the hash explicitly so changing
  them also invalidates Devbox's cached state.
- Hash each create_files source file independently, keyed by its path, instead
  of concatenating raw bytes. Concatenation is ambiguous (different
  file/content splits can yield identical byte streams) and buffered every file
  at once.

Copy link
Copy Markdown
Collaborator Author

The one red check (test (not-main, ubuntu-latest, project-tests-off, 2.19.2)) is a pre-existing flaky test unrelated to this change, not a regression from this PR.

  • The only failing testscript is add_platforms_flakeref.test, which installs an external GitHub flake (github:F1bonacc1/process-compose/v1.87.0) over the network.
  • In the same commit (07682de), the identical suite passed on nix 2.30.2 and only failed on the 2.19.2 job. A test that passes on one nix version and fails on another within the same commit is environmental (network fetch on a slower runner), not a code issue.
  • This change only touches local-plugin config hashing (plugin.Config.Hash()); it does not affect devbox add, flake refs, or this test, which uses no local plugins. All plugin-related testscripts (plugin.test, disable-plugin.test, plugin.cycle.test, nodejs_corepack_autodetect.test) and all unit/lint/build jobs are green.

I re-triggered CI once already (the flake recurred only on the 2.19.2 job). I don't have permission to re-run individual jobs via the API, so a maintainer re-run of that single job should clear it.


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