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
- Navigate to page:
vibium go "https://sahitest.com/demo/training/books.htm"
- Set localStorage and sessionStorage values:
vibium eval "localStorage.setItem('user', 'alice')"
vibium eval "sessionStorage.setItem('session_id', 'abc123')"
- Export the state:
vibium storage -o state.json
- Clear the current storage:
vibium eval "localStorage.clear(); sessionStorage.clear();"
- Restore from the exported file:
vibium storage restore state.json
- 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.
Summary
vibium storage restore <file>reports success ("Storage state restored") but does not actually restorelocalStorageorsessionStorage. Cookies are restored correctly, but any localStorage/sessionStorage values saved viavibium storage -o <file>are silently lost on restore.Environment
v26.5.31Steps to reproduce
vibium go "https://sahitest.com/demo/training/books.htm"Expected result
Step 6 should print
"alice"and"abc123", sincestorage restorereported success.Actual result
Both calls return
null. Nothing was restored, despite the CLI printing:No error is shown — the failure is completely silent.
Root cause
The exported
state.jsoncontains thestoragefield 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(inclicker/internal/agent/handlers.go) first runs a script that callsJSON.stringify(...)in the browser, and then serializes that resulting string again withjson.MarshalIndentwhen building the final output — a double-encoding bug.On restore,
browserRestoreStoragereadsstate.Storageasjson.RawMessageand interpolates it directly into a new script:Because
state.Storageis itself a JSON-encoded string (not an object), the generated script becomes:stateends up being a plain JS string, sostate.localStorageandstate.sessionStorageareundefined. Bothifblocks 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 finalstateobject, instead of assigning the raw string directly. This mirrors the pattern already used by the (unaffected) wire-API path inhandleContextStorage(clicker/internal/api/handlers_storage.go), which decodes the same kind ofJSON.stringifyresult before re-embedding it.browserRestoreStorageshould 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.