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

  1. Call browser_snapshot again to mint a fresh recovery handle and its metadata, then act with the new uids.
  2. If it persists, check the CCR store under CAVEMAN_HOME for truncation/permission problems (state writes must be atomic, mode 0600).
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/c88d75d721e205e1. Report an issue: GitHub.