Skip to content

vibium storage restore silently fails to restore localStorage/sessionStorage #217

Description

@Jhoan0714

Summary

vibium storage restore <file> reports success ("Storage state restored") but does not actually restore localStorage or sessionStorage. Cookies are restored correctly, but any localStorage/sessionStorage values saved via vibium storage -o <file> are silently lost on restore.

Environment

  • Vibium version: v26.5.31
  • OS: macOS 26.5.1 (BuildVersion 25F80)

Steps to reproduce

  1. Navigate to page:
    vibium go "https://sahitest.com/demo/training/books.htm"
  2. Set localStorage and sessionStorage values:
    vibium eval "localStorage.setItem('user', 'alice')"
    vibium eval "sessionStorage.setItem('session_id', 'abc123')"
  3. Export the state:
    vibium storage -o state.json
  4. Clear the current storage:
    vibium eval "localStorage.clear(); sessionStorage.clear();"
  5. Restore from the exported file:
    vibium storage restore state.json
  6. Read the values back:
    vibium eval "localStorage.getItem('user')"
    vibium eval "sessionStorage.getItem('session_id')"

Expected result

Step 6 should print "alice" and "abc123", since storage restore reported success.

Actual result

Both calls return null. Nothing was restored, despite the CLI printing:

Storage state restored from /path/to/state.json (0 cookies)

No error is shown — the failure is completely silent.

Root cause

The exported state.json contains the storage field as a JSON string, not a nested JSON object:

{
  "cookies": [],
  "storage": "{\"origin\":\"https://sahitest.com\",\"localStorage\":{\"user\":\"alice\"},\"sessionStorage\":{\"session_id\":\"abc123\"}}"
}

This happens because browserStorageState (in clicker/internal/agent/handlers.go) first runs a script that calls JSON.stringify(...) in the browser, and then serializes that resulting string again with json.MarshalIndent when building the final output — a double-encoding bug.

On restore, browserRestoreStorage reads state.Storage as json.RawMessage and interpolates it directly into a new script:

script := fmt.Sprintf(`(function() {
    var state = %s;
    if (state.localStorage) { ... }
    if (state.sessionStorage) { ... }
    return 'ok';
})()`, string(state.Storage))

Because state.Storage is itself a JSON-encoded string (not an object), the generated script becomes:

var state = "{\"origin\":\"https://sahitest.com\",\"localStorage\":{\"user\":\"alice\"}, ...}";

state ends up being a plain JS string, so state.localStorage and state.sessionStorage are undefined. Both if blocks are skipped, and the script still returns 'ok' — which is why the CLI reports success even though nothing was restored.

Impacted operations

  • vibium storage restore <file> (CLI)
  • browser_restore_storage (MCP tool)

Cookie restore is unaffected, since cookies are stored as a proper array (not double-encoded).

Proposed fix

In browserStorageState, avoid double-encoding: decode the JSON string returned by the in-browser script (JSON.stringify(...)) back into a Go value before embedding it into the final state object, instead of assigning the raw string directly. This mirrors the pattern already used by the (unaffected) wire-API path in handleContextStorage (clicker/internal/api/handlers_storage.go), which decodes the same kind of JSON.stringify result before re-embedding it.

browserRestoreStorage should not need any changes — it already reads the storage field as raw JSON and interpolates it directly into the restore script; it just needs the upstream data to actually be a JSON object instead of a JSON-encoded string.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions