Skip to content

Hash-based refresh for the config seed mirror — every install is pinned to the prompts it shipped with #4251

Description

@kevinthelago

ensure_seeded() mirrors the packaged src-tauri/data/ tree into ~/.base-studio-code/config/ on first boot, seed_files writes only where absent, and load_opt prefers the on-disk copy. So the config dir is written once, never updated, and always wins at read time.

Every install is therefore permanently pinned to the prompts, protocols, permissions and shell hooks it shipped with. No improvement to any of them ever reaches an existing user.

Measured, not theorised

Audited this machine's mirror against the current seed: 192 files, 44 differing, 26 retired seeds still overlaying. Not customisation — staleness. Among the 44:

File Why it matters
permissions/base.json, permissions/role-capabilities.json the security allow-list — the posture running here is not the one we ship
shell/confine.sh, coord-emit.sh, activity.sh, fleet.sh the PreToolUse hooks + bsc-* emitters — a security-hook fix silently does not apply (already seen once in #3530)
fleet/worker-protocol.md, director-protocol.md including #4191's "read from the stores, not copies" precedence rule
planner/process.md, intro.*, stage-registry.json the planner's core spec
all 6 blueprints/*.json, 5 personas/*.json, 4 stages/*.json

stages/features.json here had no requires field at all — the entire algorithm-library / reuse-first instruction (#4191/#3892) had never reached the planner running on this machine.

And because the frontend overlay is additive by stem, the 26 retired seeds still apply: the deleted complete / feature-add / serverless-function / ui-kit / blueprint-author blueprints were still being offered.

The codebase already diagnosed this — for one directory

SEED_SKIP_DIRS = ["components"] exists verbatim because "an auto-seeded copy became a de-facto override that pinned every install's effective seed at its first-run roster — refresh/retire verdicts could never fire (#2514)". components/ was excluded from seeding and given a hash-based refresh (#2483). Every other tree still has the bug.

The fix — hash-based refresh, mirroring #2483

Record the hash of what was seeded, per file, in a manifest outside the overlaid tree (a file inside config/ would itself be served by get_config_files). Then on boot, per seed file:

  • absent → write it, record the hash (today's behaviour)
  • present, hash matches what we seeded → the user never touched it: overwrite with the new seed, update the hash
  • present, hash differs → a genuine edit: leave it, and surface that it has diverged so it is visible rather than silent
  • seed gone, copy unmodifiedremove it, which is what stops retired blueprints resurrecting
  • seed gone, copy modified → leave it, surface it as orphaned

Chosen over the alternative (stop seeding these trees, as #2514 did for components/) because the overlay exists so config can be customised, and silently dropping that is a behaviour change for anyone who has. Hash-based keeps the capability and makes staleness impossible.

Acceptance

  • A manifest of seeded hashes, stored outside config/.
  • The five cases above, unit-tested against a temp home — in particular that a modified file is never clobbered and an unmodified one always refreshes.
  • Retired seeds are removed when unmodified.
  • Divergences are surfaced, not silent.
  • Migration for existing installs: no manifest yet ⇒ treat every file as unknown. Do not clobber — hash it against the shipped seed and, where it matches, adopt it into the manifest; where it does not, leave it and report it as diverged (it may be stale rather than edited, and we cannot tell them apart without history).
  • Gate: cargo check / clippy --all-targets / cargo test.

Note

This machine's mirror was backed up and cleared by hand while diagnosing (~/.base-studio-code/config.bak-20260802T030849Z), so it is currently clean — which means it is not a test case for the migration path. Test that against a synthesised stale home.

Related: #2483 · #2514 · #2027 · #3530.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions