Skip to content

wp_options row keys silently lost after Studio site restart (partial options corruption) #52

Closed

Description

@chubes4

Summary

On a Studio site running markdown-database-integration, some recently-added keys inside a serialized array option vanished after a studio site stop + studio site start cycle. Not the whole option row — the row persists with its original keys, but a subset of keys added within the option's PHP-serialized array value silently disappear.

This is observably distinct from, but likely related to, #47 (cold-boot wp_posts corruption via ALTER TABLE CHANGE). That issue corrupts a whole table; this one drops specific keys from a serialized array inside a single option row.

Observed reproduction (not yet 100% deterministic)

Setup

  • Studio site intelligence-chubes4 running markdown-database-integration (development checkout at /Users/chubes/Developer/markdown-database-integration/ symlinked into the site)
  • Data Machine installed with datamachine_settings option storing a PHP-serialized array of scalar + array settings
  • Studio dev build (combined fixes branch)

Sequence that triggered the loss

  1. Via wp datamachine settings set, set these keys sequentially:
    • default_model = gpt-5.4-mini
    • default_provider = openai
    • daily_memory_enabled = true
    • context_models = { pipeline: {...}, chat: {...}, system: {...} }
    • agent_models = { ... } (auto-mirrored from context_models by the DM settings ability)
  2. Ran studio site stop
  3. Ran studio site start
  4. After restart, inspected get_option('datamachine_settings')

Result

Four of the nine keys were missing from the post-restart option value. The surviving keys were the ones present in the option before this batch of writes (disabled_tools, enabled_pages, site_context_enabled, cleanup_job_data_on_failure). Only default_model — the first of the new keys written — survived. default_provider, daily_memory_enabled, context_models, agent_models were all gone.

Second restart test (not reproducing on this attempt)

Re-added the missing keys. Stopped + started the site again. All 9 keys survived.

So this is intermittent or tied to a specific boot codepath, not every stop/start cycle. Suspected trigger: cold boot where SQLite index is rebuilt from markdown files, vs warm boot where the index is reused. The first restart (which triggered the loss) may have been a cold-boot case because the dev CLI had been freshly rebuilt and a marker file may have been invalidated.

Why this is probably markdown-database-integration

  • The wp_options row itself persists — this isn't a Studio-injects-fresh-WP problem.
  • The loss is at the serialized-array-key level — something is deserializing, dropping keys, and re-serializing. That matches the MDI markdown-to-SQLite reload pattern more than any other component on the stack.
  • Related existing bug: issue Plugin table schema loading corrupts wp_posts data during cold boot #47 documents ALTER TABLE CHANGE (MySQL syntax) failing in SQLite, triggering rebuild_information_schema() which corrupts wp_posts. A similar fallback corrupting wp_options row contents is a plausible mechanism.
  • If datamachine_settings has a corresponding markdown-backed source file and MDI reloads it on boot, stale content from before the recent writes could overwrite fresh in-DB content. Worth checking whether wp_options rows are tracked by MDI at all and how they round-trip.

Why this matters

Local Studio development of any plugin that uses serialized-array options (most of WordPress) is exposed to silent data loss on restart. Symptoms are hard to diagnose because:

  • The option row is still present
  • Older keys are still present
  • Only the most-recently-added or most-recently-modified keys vanish
  • Nothing fails loudly — the plugin reads the option, finds a partial config, and behaves unexpectedly

This hit Data Machine twice in a 30-minute session today (first time explained by a full cold boot + the SQLite index delete workaround for #47; second time inexplicable to me without reading MDI's internals).

Diagnostic information

Option state immediately before failure

9 keys inside datamachine_settings:

disabled_tools, enabled_pages, site_context_enabled, cleanup_job_data_on_failure,
default_model, default_provider, daily_memory_enabled, context_models, agent_models

Option state after the restart that triggered loss

disabled_tools, enabled_pages, site_context_enabled, cleanup_job_data_on_failure,
default_model

wp_options row count and related rows (after re-adding missing keys)

  • Total wp_options: 178
  • datamachine_settings length: 236 bytes (matches all 9 keys serialized)
  • chubes_ai_http_shared_api_keys length: 192 bytes (survived in both cases)
  • Note: datamachine_network_settings was already missing — never set, so not a data loss.

Environment

  • Studio dev build (1.7.8-beta4), combined-fixes branch
  • Playground combined-fixes branch with PR #3494 merged
  • SQLite via markdown-database-integration + bundled sqlite-database-integration
  • No db.php drop-in customizations beyond what MDI installs

Requests

  1. Confirm whether wp_options rows are round-tripped through markdown files by MDI. If yes, describe where datamachine_settings-equivalent option content would be written to disk. If no, investigate what other MDI mechanism could partially clobber an option value.
  2. Check the cold-boot vs warm-boot path distinction for option rehydration. The first restart in my session may have been a cold-boot equivalent (SQLite index stale or loader forced rebuild) while the second was warm. If there's a branch where MDI reads pre-existing values from disk and the disk content is stale, that's the failure mode.
  3. Relationship to Plugin table schema loading corrupts wp_posts data during cold boot #47. Is this the same root cause manifesting in a different table? Or a separate fallback path?

Testing clue for whoever picks this up

If the "trigger is cold boot where index is rebuilt" hypothesis is right, the simplest deterministic reproduction is:

  1. Set the options
  2. Delete wp-content/markdown-index.sqlite (forces cold boot on next start, per Plugin table schema loading corrupts wp_posts data during cold boot #47 workaround)
  3. Start the site
  4. Check options — the vanished ones should match the pattern observed here (recently-added keys inside a serialized array option)

I haven't tried this yet — happy to when time allows, but it's the first experiment worth running.

Related

Activity

  1. chubes4 commented on Apr 17, 2026

    @chubes4
    CollaboratorAuthor

    Root cause identified — not an MDI bug

    Spent a session reproducing this today. The filed hypothesis (register_shutdown_function not firing) turned out to be wrong. The real cause is Studio issue #3095 — Studio overwrites wp-content/db.php on site start, reverting it to stock SQLite integration. MDI silently stops loading.

    The sequence that produces the symptom

    1. studio site start → Studio replaces MDI's db.php drop-in with stock SQLite integration
    2. MDI is no longer the database backend for the running site
    3. Subsequent wp option set / update_option() writes go to wp-content/database/.ht.sqlite (stock SQLite), not to MDI's in-memory DB
    4. MDI's WP_Markdown_Write_Engine never sees those writes, so options.json is never re-flushed
    5. Eventually the site restarts (or the SQLite index is deleted for unrelated reasons) and a cold boot triggers → MDI reads the now-stale options.json to rehydrate SQLite → recent option keys are missing

    What made it look like a shutdown-flush bug

    The smoking-gun observation in the original report was "options.json mtime is hours old despite successful WP-CLI writes." That looked like the register_shutdown_function flush wasn't firing. What was actually happening:

    • db.php had been overwritten hours earlier
    • Every subsequent CLI write bypassed MDI entirely
    • MDI had nothing to flush, because MDI's write engine was never invoked

    Verification that shutdown hooks are fine

    Tested both register_shutdown_function and add_action('shutdown', ...) under studio wp eval — both fire correctly. The earlier "shutdown not firing" test was a false negative caused by writing probe logs to /tmp, which Studio doesn't mount into the Playground WASM filesystem (paths outside the site folder silently fail). Writing probes into wp-content/ confirmed shutdown hooks run cleanly on both web requests and CLI invocations.

    Deterministic repro of the full lifecycle with MDI properly loaded

    After restoring MDI's db.php and confirming it's the active drop-in:

    1. wp option patch insert datamachine_settings final_repro_check "value-$(date +%s)" → immediately visible in options.json (write engine flushed on shutdown) ✅
    2. studio site stop → db.php preserved (on Studio dev fork with #3095 merged)
    3. rm wp-content/markdown-index.sqlite → force cold boot on next start
    4. studio site start → cold boot rehydrates from options.json
    5. wp option get datamachine_settings → final_repro_check survives ✅

    No data loss. The write engine works as designed.

    Fix / recommendation

    This issue is resolved upstream by Studio #3095 (preserve custom db.php drop-ins). On the Studio dev fork where #3095 is merged, the symptom does not reproduce.

    For users on vanilla Studio, the workaround until #3095 lands is to re-copy MDI's db.php into wp-content/db.php after every studio site start. A defensive option in MDI would be to verify the drop-in is still MDI's on plugin load and re-install if not, but that feels like papering over a Studio bug that already has a fix in flight.

    Closing unless there's a scenario I missed where MDI alone reproduces the loss.

    Related

  2. chubes4 commented on Apr 21, 2026

    @chubes4
    CollaboratorAuthor

    Closing — root-caused as Studio #3095 (Studio overwrites wp-content/db.php on site start, reverting MDI's drop-in to stock SQLite integration). Fixed upstream on the Studio dev fork where #3095 is merged; the symptom does not reproduce there.

    Not an MDI bug. The original hypothesis about register_shutdown_function was a false lead — shutdown hooks fire correctly, the earlier probe was just writing logs to /tmp which Studio doesn't mount into the Playground WASM filesystem (silent fail).

    See the full repro + resolution in the comment above.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions