Skip to content

fix(ui): scope avatar storage per agent ID#91533

Closed
dwc1997 wants to merge 8 commits into
openclaw:mainfrom
dwc1997:fix/90890-avatar-per-agent
Closed

fix(ui): scope avatar storage per agent ID#91533
dwc1997 wants to merge 8 commits into
openclaw:mainfrom
dwc1997:fix/90890-avatar-per-agent

Conversation

@dwc1997

@dwc1997 dwc1997 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: The avatar storage used a single global localStorage key openclaw.control.assistant.v1, causing setting an avatar for one agent to overwrite it for all agents.
  • Why now: Users with multiple agents (e.g. main, worker) cannot set different avatars for each agent. Setting an avatar for one agent silently overwrites the avatar for all other agents via the shared key.
  • Outcome: Scope the localStorage key per agent ID: openclaw.control.assistant.v1:{agentId}. When clearing a scoped avatar, store null explicitly to prevent fallback to the global key. Include backward compatibility: existing global avatars are used as fallback when no agent-specific avatar exists.
  • Out of scope: No changes to avatar upload, display, or server-side storage.
  • Reviewer focus: Whether the localAssistantIdentityKey() function correctly scopes keys, whether the explicit null prevents legacy fallback, and whether the global fallback is preserved for new agents.

Linked context

Closes #90890

Real behavior proof

  • Behavior or issue addressed: Setting avatar for one agent changes it for all agents; clearing a scoped avatar incorrectly falls back to the legacy global avatar -- [Bug]: You cannot give an avatar to a specific agent in settings #90890.
  • Real environment tested: Windows 10, Node.js v22.22.3, vitest 4.1.7, local checkout on branch fix/90890-avatar-per-agent.
  • Exact steps or command run after this patch: Ran npx vitest run ui/src/ui/controllers/assistant-identity.test.ts ui/src/ui/controllers/proof-avatar-per-agent.proof.test.ts --config test/vitest/vitest.ui.config.ts --reporter verbose. The proof test (proof-avatar-per-agent.proof.test.ts) imports and exercises the real loadLocalAssistantIdentity() and saveLocalAssistantIdentity() functions from ui/src/ui/storage.ts against an in-memory localStorage mock via vi.stubGlobal("localStorage", createStorageMock()). The regression test (assistant-identity.test.ts) exercises the real setAssistantAvatarOverride() controller function which delegates to those same storage functions.
  • Evidence after fix:
    RUN  v4.1.7 D:/JavaUp/openclaw
    
    ✓ |ui| ui/src/ui/controllers/proof-avatar-per-agent.proof.test.ts > avatar storage per agent ID — real code proof (#90890) > Test 1: per-agent scoping — main and worker have independent avatars 4ms
    ✓ |ui| ui/src/ui/controllers/proof-avatar-per-agent.proof.test.ts > avatar storage per agent ID — real code proof (#90890) > Test 2: clear isolation — clearing main does not affect worker 1ms
    ✓ |ui| ui/src/ui/controllers/proof-avatar-per-agent.proof.test.ts > avatar storage per agent ID — real code proof (#90890) > Test 3: global fallback — new agent without scoped key falls back to global 0ms
    ✓ |ui| ui/src/ui/controllers/proof-avatar-per-agent.proof.test.ts > avatar storage per agent ID — real code proof (#90890) > Test 4: scoped clear blocks legacy global fallback (upgrade scenario) 1ms
    ✓ |ui| ui/src/ui/controllers/proof-avatar-per-agent.proof.test.ts > avatar storage per agent ID — real code proof (#90890) > Test 5: explicit null stored in scoped key to prevent fallback 0ms
    ✓ |ui| ui/src/ui/controllers/proof-avatar-per-agent.proof.test.ts > avatar storage per agent ID — real code proof (#90890) > Test 6: localStorage key naming — scoped keys use colon separator 1ms
    ✓ |ui| ui/src/ui/controllers/assistant-identity.test.ts > loadAssistantIdentity > ignores stale identity responses after the active session changes 6ms
    ✓ |ui| ui/src/ui/controllers/assistant-identity.test.ts > setAssistantAvatarOverride > persists the assistant avatar locally and mirrors the user avatar pattern 1ms
    ✓ |ui| ui/src/ui/controllers/assistant-identity.test.ts > setAssistantAvatarOverride > clears the local override 1ms
    ✓ |ui| ui/src/ui/controllers/assistant-identity.test.ts > setAssistantAvatarOverride > scopes avatar storage per agent ID (regression #90890) 1ms
    ✓ |ui| ui/src/ui/controllers/assistant-identity.test.ts > setAssistantAvatarOverride > falls back to global key when agent-specific key is not set 0ms
    ✓ |ui| ui/src/ui/controllers/assistant-identity.test.ts > setAssistantAvatarOverride > handles legacy global key after scoped clear (upgrade scenario) 1ms
    
    Test Files  2 passed (2)
         Tests  12 passed (12)
    
  • Observed result after fix: All 12 tests pass across 2 test files. Per-agent scoping works: main and worker have independent avatars. Clearing one agent does not affect others. New agents without scoped keys fall back to the global key. Scoped clears store explicit {\"avatar\":null} to prevent legacy fallback. The setAssistantAvatarOverride controller correctly passes state.assistantAgentId to storage functions, and loadAssistantIdentity reads scoped local overrides.
  • What was not tested: Live Control UI test with multiple agents in a running gateway (requires gateway setup with multiple agent configurations).

Tests and validation

  • Regression: ui/src/ui/controllers/assistant-identity.test.ts -- 6/6 passed
  • Proof: ui/src/ui/controllers/proof-avatar-per-agent.proof.test.ts -- 6/6 passed
  • Total: 12/12 passed

Risk checklist

  • userVisible: Yes (avatars now scoped per agent; legacy fallback behavior changed for scoped clears)
  • configEnvMigration: No (backward compatible with global key)
  • securityAuthNetwork: No
  • Mitigation: Backward compatible; existing global avatars used as fallback for new agents. Scoped clears now store explicit null to prevent unexpected legacy fallback.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: XS proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 9, 2026
@clawsweeper

clawsweeper Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I did a careful shell check against current main, and this is already implemented.

Current main already fixes the linked assistant-avatar overwrite bug via the merged canonical PR, and this open branch now carries an alternate older localStorage layout rather than unique remaining work.

Root-cause cluster
Relationship: superseded
Canonical: #93712
Summary: The original per-agent assistant-avatar localStorage bug is fixed by the merged canonical PR; this PR and earlier closed attempts are alternate implementations of the same root cause.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review details

Best possible solution:

Keep the merged current-main per-agent avatar map from #93712 and close this alternate branch instead of merging a second localStorage scheme.

Do we have a high-confidence way to reproduce the issue?

Yes for the original bug on the PR base and latest shipped release: the old storage shape used one unscoped assistant-avatar key. No current-main failure remains because the merged fix now stores and reads overrides by agent ID.

Is this the best way to solve the issue?

No for this branch now. The best fix has already landed in #93712 with broader render/controller/test coverage and a cleaner one-key per-agent map migration.

Security review:

Security review cleared: No concrete security or supply-chain concern was found; the obsolete diff is limited to Control UI localStorage handling and adjacent Vitest tests.

AGENTS.md: found and applied where relevant.

What I checked:

  • Current main stores assistant avatars per agent: Current main parses a persisted avatar map, keys avatars by normalized agent ID, migrates a legacy global avatar to one concrete agent, and returns only the active agent's avatar. (ui/src/ui/storage.ts:374, ec3f76b3806a)
  • Current main threads the active agent ID through callers: The assistant identity controller loads local overrides with state.assistantAgentId and saves overrides with the passed agentId, so the Control UI path is no longer unscoped. (ui/src/ui/controllers/assistant-identity.ts:71, ec3f76b3806a)
  • Current main has regression coverage: The main-branch controller tests cover per-agent isolation, clearing one agent without affecting another, legacy global migration to the first loaded agent, and prototype-like agent IDs. (ui/src/ui/controllers/assistant-identity.test.ts:130, ec3f76b3806a)
  • Merged PR provenance: Live PR metadata shows fix: scope assistant avatar override to agent ID #93712 merged on 2026-06-16T18:53:36Z, closed the original avatar issue, and touched storage, controller, render, bootstrap, and focused tests. (4aba273939e8)
  • Git history provenance: The merge commit message explicitly describes the unscoped localStorage bug and the fix: include agentId when saving and filter by agentId when loading. (4aba273939e8)
  • Current-main-only release provenance: No tag contains the fix commit, and the latest listed release v2026.6.8 was published before the merged fix, so this is implemented on current main but not yet proven shipped in a release. (4aba273939e8)

Likely related people:

  • lsr911: Authored the merged PR that fixed the same per-agent assistant-avatar storage bug and closed the linked issue. (role: merged fix author; confidence: high; commits: 8f0ff67c7e4e, 4aba273939e8; files: ui/src/ui/storage.ts, ui/src/ui/controllers/assistant-identity.ts, ui/src/ui/app-render.ts)
  • vincentkoc: Merged the canonical fix and authored the follow-up commits in that PR that persisted assistant avatars per agent and satisfied scoped avatar checks. (role: merger and fix extender; confidence: high; commits: 2afa5215cf8c, 663e80d6b755, 4aba273939e8; files: ui/src/ui/storage.ts, ui/src/ui/controllers/assistant-identity.ts, ui/src/ui/app-render.assistant-avatar.test.ts)
  • Peter Steinberger: Git history around assistant identity and avatar support includes earlier Control UI assistant/agent avatar work, making this a useful fallback routing candidate for related UI identity behavior. (role: historical adjacent identity/avatar contributor; confidence: medium; commits: 8544df36b87d, a59ac5cf6f94, a2bea8e3666c; files: ui/src/ui/controllers/assistant-identity.ts, ui/src/ui/app-render.ts)

Codex review notes: model internal, reasoning high; reviewed against ec3f76b3806a; fix evidence: commit 4aba273939e8, main fix timestamp 2026-06-16T18:53:35Z.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 9, 2026
@dwc1997
dwc1997 force-pushed the fix/90890-avatar-per-agent branch from ff6e8f9 to 865c48b Compare June 14, 2026 07:37
@openclaw-barnacle openclaw-barnacle Bot added size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: supplied External PR includes structured after-fix real behavior proof. and removed size: XS proof: supplied External PR includes structured after-fix real behavior proof. triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 14, 2026
@dwc1997
dwc1997 force-pushed the fix/90890-avatar-per-agent branch from 865c48b to 2207c0a Compare June 14, 2026 09:02
@dwc1997

dwc1997 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@dwc1997

dwc1997 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@dwc1997

dwc1997 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jun 14, 2026
@dwc1997

dwc1997 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper clawsweeper Bot removed the rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. label Jun 15, 2026
@openclaw-barnacle openclaw-barnacle Bot added the proof: supplied External PR includes structured after-fix real behavior proof. label Jun 15, 2026
@dwc1997

dwc1997 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 15, 2026
@dwc1997
dwc1997 force-pushed the fix/90890-avatar-per-agent branch from 5da32e7 to 67b92ee Compare June 16, 2026 00:26
@clawsweeper clawsweeper Bot added status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 16, 2026
@dwc1997
dwc1997 force-pushed the fix/90890-avatar-per-agent branch 2 times, most recently from af9cded to 2dd5cc3 Compare June 17, 2026 13:14
dwc1997 and others added 8 commits June 17, 2026 21:29
The avatar storage used a single global localStorage key, causing
setting an avatar for one agent to overwrite it for all agents.

Scope the localStorage key per agent ID with backward compatibility:
existing global avatars are used as fallback when no agent-specific
avatar exists.

Fixes openclaw#90890

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Regression test for openclaw#90890: verifies that avatar storage is scoped
per agent ID so setting an avatar for one agent does not overwrite
another agent's avatar, and that the global key is used as fallback
when no agent-specific avatar exists.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
When clearing a scoped agent avatar, store null explicitly in the
scoped key instead of removing it. This prevents loadLocalAssistantIdentity
from falling back to the legacy global key after a user explicitly
clears an agent's avatar.

Also adds regression coverage for the legacy global key upgrade scenario:
seeded legacy global → scoped set → scoped clear → other agent still
sees legacy global.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds proof-avatar-per-agent.proof.test.ts that exercises the real
loadLocalAssistantIdentity and saveLocalAssistantIdentity functions
from storage.ts against an in-memory localStorage mock. Covers 6
scenarios: per-agent scoping, clear isolation, global fallback,
scoped clear blocks legacy fallback, explicit null storage, and
localStorage key naming convention.

All 12 tests pass (6 regression + 6 proof).

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Fixes oxlint error: Replace single-line if body with braced block.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@dwc1997
dwc1997 force-pushed the fix/90890-avatar-per-agent branch from 2dd5cc3 to 70a0b54 Compare June 17, 2026 13:29
@clawsweeper clawsweeper Bot added status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. labels Jun 17, 2026
@clawsweeper

clawsweeper Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: You cannot give an avatar to a specific agent in settings

1 participant