JuliusBrussee/caveman · error
empty a11y recovery metadata
Error message
empty a11y recovery metadata
What it means
Returned by Session.targetsForHandle() when s.eng.RetrieveMetadata(handle) succeeds but returns zero bytes for the a11y recovery handle. The metadata blob is where the compressed snapshot stores the uid -> Target map; empty bytes means the handle exists but its payload was never written (or was truncated), so there is no uid map to decode and the act path fails rather than acting on fabricated targets.
Source
Thrown at browse/session.go:213
s.replaceTargets(targets)
return mcp.ToolRawText(text)
}
func snapshotTimeout(wait time.Duration) time.Duration {
base := 15 * time.Second
if wait > 0 {
base += wait
}
return base
}
func (s *Session) targetsForHandle(handle string) (map[string]Target, error) {
meta, err := s.eng.RetrieveMetadata(handle)
if err != nil {
return nil, err
}
if len(meta) == 0 {
return nil, errors.New("empty a11y recovery metadata")
}
var decoded struct {
UIDs map[string]Target `json:"uids"`
}
if err := json.Unmarshal(meta, &decoded); err != nil {
return nil, err
}
if decoded.UIDs == nil {
decoded.UIDs = map[string]Target{}
}
return decoded.UIDs, nil
}
func finalizeSnapshotPayload(payload snapshotPayload) (snapshotPayload, string) {
counter := tokens.Default()
for range 8 {
encoded, _ := json.Marshal(payload)
delivered := counter.Count(encoded)View on GitHub (pinned to 27d5a3981a)
Solutions
- Call browser_snapshot again to mint a fresh recovery handle and its metadata, then act with the new uids.
- If it persists, check the CCR store under CAVEMAN_HOME for truncation/permission problems (state writes must be atomic, mode 0600).
- Never replay handles across sessions — they name compressed state that may have been reclaimed.
Example fix
// before act(handle: "h-169...", target: "uid-9", action: "click") // metadata empty // after res = browser_snapshot() act(handle: res.recovery_handle, target: "<uid from res>", action: "click")
Defensive patterns
Strategy: validation
Validate before calling
// Go: verify recovery metadata is present before acting
func (s *Session) targetsForHandle(handle string) (map[string]Target, error) {
meta, err := s.eng.RetrieveMetadata(handle)
if err != nil {
return nil, err
}
if len(meta) == 0 {
return nil, errors.New("empty a11y recovery metadata") // re-snapshot upstream
}
// ...
} Try / catch
// Go: on empty metadata, mint a new snapshot instead of failing the whole session
if errors.Is(err, errEmptyRecoveryMeta) {
snap, _ := s.snapshot(ctx)
return s.act(ctx, snap.Handle, target, action)
} Prevention
- Always act with the recovery handle returned by the most recent browser_snapshot call.
- Never persist or replay handles across sessions — CCR state may be reclaimed.
- Keep CAVEMAN_HOME state writes atomic (mode 0600) and avoid pruning the store mid-session.
When it happens
Trigger: Calling an act/query tool with a recovery handle whose CCR metadata entry is empty: snapshot wrote the handle but metadata persistence failed, the store was pruned between snapshot and act, or a handle was constructed/replayed rather than obtained from browser_snapshot.
Common situations: Agent reuses a recovery handle from a previous session after state cleanup; CCR store corrupted or reset under a fresh CAVEMAN_HOME; crash between snapshot payload write and metadata write.
Related errors
- ${agent} caveman-cloud MCP changed during interrupted setup;
- ${agent} caveman-cloud MCP changed during interrupted remova
- browser returned no result object
- engine_changed_bytes_without_recovery
- cave_recovery_handle_out_of_scope
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/c88d75d721e205e1.
Report an issue: GitHub.