You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracks the same bug as #437 (user-facing report: /daily binding causes cross-user data leakage). This issue captures the engineering analysis and fix direction; #437 is the QA-side reproduction.
Symptom
I shared my Lark bot with a colleague. After they ran /daily and bound their GitHub username (Yuezh0127), my own /daily started reporting on Yuezh0127 instead of my GitHub account. Re-binding mine just flips it again — last writer wins. The GitHub username binding is behaving as a single global value per bot, not per Lark user.
Reproduction:
User A (bot owner) runs /daily in Lark, binds GitHub username alice.
User B (added to the same Lark bot) runs /daily, binds GitHub username bob.
User A runs /daily again → daily report scheduled for bob. A's binding is gone.
(Screenshot in original report: my /daily shows Daily report scheduled for Yuezh0127 — Yuezh0127 is the colleague's GitHub account, not mine.)
Also reproduces in 1:1 private chats with the bot, not only in group chats (see #437).
Root cause
UserConfigGAgent is keyed by bot registration scope, not by Lark user identity. All Lark users sharing one bot share one UserConfigGAgent actor (user-config-{registrationScopeId}), so any user's SaveGithubUsernameAsync overwrites the binding for every other user of that bot.
The relevant chain:
agents/Aevatar.GAgents.ChannelRuntime/ChannelConversationTurnRunner.cs:787 — when an inbound Lark message is converted to ChannelInboundEvent, RegistrationScopeId is set to registration.ScopeId (a per-bot value). The per-Lark-user SenderId is also populated (line 779) but is not propagated downstream as the user-config scope.
agents/Aevatar.GAgents.ChannelRuntime/AgentBuilderCardFlow.cs:79-81 — when prefilling the daily-report form, the saved username is read using evt.RegistrationScopeId:
src/Aevatar.Studio.Projection/QueryPorts/ProjectionUserConfigQueryPort.cs:41 — read uses the same key, so all Lark users in the bot read the same record.
The proto already carries the per-Lark-user identity (ChannelInboundEvent.sender_id, field 2 in agents/Aevatar.GAgents.ChannelRuntime/channel_runtime_messages.proto); it's just not threaded into the user-config scope.
How this slipped in
Issue #327 (closed) added GithubUsername to UserConfig under the assumption "Day One 的现实是'一个 user 关心一个 github',跨对话共享是 feature 不是 bug." That was correct for a single-owner CLI/Tools UI scenario, where the scope resolver returns a per-NyxID-user scope. But on the Lark adapter the scope is the bot's registration scope (one bot = one NyxID identity = one scope), and the bot is shared across many Lark users. The CLI semantics (one identity per scope) and the Lark semantics (many Lark users per scope) collide on the same actor.
Suggested fix direction (not prescriptive)
The fix has to give each Lark user their own UserConfigGAgent. Two shapes worth considering:
Composite scope: thread ChannelInboundEvent.SenderId through AgentToolRequestContext and use a composite key like {registrationScopeId}:lark:{senderId} for the user-config actor on Lark inbound paths. CLI/Tools paths keep their existing scope. This is the smallest blast radius.
Channel-user-scoped actor: introduce a per-platform-user identity that the Lark adapter resolves (probably tying back to ChannelUserBindingGAgent, mentioned in Add GithubUsername to UserConfig for /daily template prefill #327's binding decomposition) and use that as the user-config scope. Cleaner long-term, but bigger surface change.
Either way:
The sender_id (or a normalized channel-user-id) needs to flow through AgentToolRequestContext → AgentBuilderTool → IUserConfigCommandService and through AgentBuilderCardFlow → IUserConfigQueryPort.
Existing data keyed at the bot-registration scope is now ambiguous. Probably acceptable to let each user re-bind on first /daily (the form already prompts when no username is prefilled), and clean up the orphaned shared record out-of-band.
Acceptance criteria
Two Lark users in the same bot can each bind a different GitHub username via /daily, and each user's /daily schedules the report for their own GitHub account.
Tests cover: (a) two distinct sender_ids in the same RegistrationScopeId round-trip independent GithubUsername values; (b) prefill returns each user's own value, not the most recent writer's.
CLI / Tools UI path (UserConfigEditor.tsx) is unaffected — single-user-per-scope semantics there should not regress.
Symptom
I shared my Lark bot with a colleague. After they ran
/dailyand bound their GitHub username (Yuezh0127), my own/dailystarted reporting onYuezh0127instead of my GitHub account. Re-binding mine just flips it again — last writer wins. The GitHub username binding is behaving as a single global value per bot, not per Lark user.Reproduction:
/dailyin Lark, binds GitHub usernamealice./daily, binds GitHub usernamebob./dailyagain → daily report scheduled forbob. A's binding is gone.(Screenshot in original report: my
/dailyshowsDaily report scheduled for Yuezh0127— Yuezh0127 is the colleague's GitHub account, not mine.)Also reproduces in 1:1 private chats with the bot, not only in group chats (see #437).
Root cause
UserConfigGAgentis keyed by bot registration scope, not by Lark user identity. All Lark users sharing one bot share oneUserConfigGAgentactor (user-config-{registrationScopeId}), so any user'sSaveGithubUsernameAsyncoverwrites the binding for every other user of that bot.The relevant chain:
agents/Aevatar.GAgents.ChannelRuntime/ChannelConversationTurnRunner.cs:787— when an inbound Lark message is converted toChannelInboundEvent,RegistrationScopeIdis set toregistration.ScopeId(a per-bot value). The per-Lark-userSenderIdis also populated (line 779) but is not propagated downstream as the user-config scope.agents/Aevatar.GAgents.ChannelRuntime/AgentBuilderCardFlow.cs:79-81— when prefilling the daily-report form, the saved username is read usingevt.RegistrationScopeId:agents/Aevatar.GAgents.ChannelRuntime/AgentBuilderTool.cs:186-187— when creating the daily-report agent, the same bot-level scope is used:And on save (line 1579):
src/Aevatar.Studio.Projection/CommandServices/ActorDispatchUserConfigCommandService.cs:77— the actor ID is derived purely from the scope:src/Aevatar.Studio.Projection/QueryPorts/ProjectionUserConfigQueryPort.cs:41— read uses the same key, so all Lark users in the bot read the same record.The proto already carries the per-Lark-user identity (
ChannelInboundEvent.sender_id, field 2 inagents/Aevatar.GAgents.ChannelRuntime/channel_runtime_messages.proto); it's just not threaded into the user-config scope.How this slipped in
Issue #327 (closed) added
GithubUsernametoUserConfigunder the assumption "Day One 的现实是'一个 user 关心一个 github',跨对话共享是 feature 不是 bug." That was correct for a single-owner CLI/Tools UI scenario, where the scope resolver returns a per-NyxID-user scope. But on the Lark adapter the scope is the bot's registration scope (one bot = one NyxID identity = one scope), and the bot is shared across many Lark users. The CLI semantics (one identity per scope) and the Lark semantics (many Lark users per scope) collide on the same actor.Suggested fix direction (not prescriptive)
The fix has to give each Lark user their own
UserConfigGAgent. Two shapes worth considering:ChannelInboundEvent.SenderIdthroughAgentToolRequestContextand use a composite key like{registrationScopeId}:lark:{senderId}for the user-config actor on Lark inbound paths. CLI/Tools paths keep their existing scope. This is the smallest blast radius.ChannelUserBindingGAgent, mentioned in Add GithubUsername to UserConfig for /daily template prefill #327's binding decomposition) and use that as the user-config scope. Cleaner long-term, but bigger surface change.Either way:
sender_id(or a normalized channel-user-id) needs to flow throughAgentToolRequestContext→AgentBuilderTool→IUserConfigCommandServiceand throughAgentBuilderCardFlow→IUserConfigQueryPort./daily(the form already prompts when no username is prefilled), and clean up the orphaned shared record out-of-band.Acceptance criteria
/daily, and each user's/dailyschedules the report for their own GitHub account./dailybinding causes cross-user data leakage #437).sender_ids in the sameRegistrationScopeIdround-trip independentGithubUsernamevalues; (b) prefill returns each user's own value, not the most recent writer's.UserConfigEditor.tsx) is unaffected — single-user-per-scope semantics there should not regress.SkillRunnerGAgent.State.OutboundConfig.GithubUsernamesnapshot semantics from Add GithubUsername to UserConfig for /daily template prefill #327 stay intact (running agents are not retroactively repointed when a user re-binds).Affected files (for triage)
Related: #437 (user-facing duplicate of this bug), #327 (introduced the binding), #254 (multi-channel adapter RFC — same multi-tenant concern surfaces here).