fix(chat): stop an empty localize erasing a conv's topic name - #29537
Open
chrisnojima wants to merge 3 commits into
Open
fix(chat): stop an empty localize erasing a conv's topic name#29537chrisnojima wants to merge 3 commits into
chrisnojima wants to merge 3 commits into
Conversation
A conversation's topic name is only ever known from its max METADATA message (localizer.go:883). A delete-history purges that message's body, so the message survives as valid-but-empty, the type switch matches nothing, and the conv localizes with an empty name. MergeLocalMetadata then wrote that empty string over the name already cached, unconditionally. Two symptoms, one cause. The channel renders blank in the inbox, since inbox rows read LocalMetadata.TopicName. And FindGeneralConvFromTeamID looks #general up by topic name, so the lookup misses the conv for good - which is a team's Emoji tab staying empty forever and the bot install modal never resolving a conversation. Both were reproduced on a real account: chat.1.local.findGeneralConvFromTeamID returned SC_NOT_FOUND while the conv itself held 901 custom emoji. MergeLocalMetadata now keeps the cached name when a localize comes back empty, and falls back to the default team topic for a conv marked IsDefaultConv, which repairs a cache an older client already poisoned. FindGeneralConvFromTeamID retries without the topic name and picks the conv by IsDefaultConv, so an unrepaired cache resolves immediately rather than waiting for the next localize.
The storage test read the inbox with a topic-name query that had never been merged, and Inbox.Read answers only queries it has seen, so it would have returned MissError in CI rather than the conv. Merge the query first, the way TestInboxQueries already does, and look conversations up by convID, which skips that gate entirely. Two branches were also asserted but unreached: the cached-name-wins arm of the merge switch, and the lookup finding no default conv at all - the one path where the fallback could now return a conversation where the old code refused. Both covered. The preserved name is no longer "general", so the test cannot pass by writing the default in the wrong branch. Also copy the query before clearing its topic name rather than mutating the caller's, and guard the mock conv lookup instead of dereferencing it.
Replaces the two-site fix from the previous commits with the one line that covers both symptoms. A conv only ever learns its topic name from its max METADATA message. A delete-history purges that message's body, so it survives as valid-but-empty, the type switch matches nothing, and the conv localizes with no name. The purge is server-side data, so this happens to every member of the team - not to one poisoned cache. Fixing it at the localizer fixes everything downstream at once. A query by topic name filters on the localized name (inboxsource.go:60), which is why repairing the cached copy could never make the #general lookup hit: it went on matching an empty string. Naming the conv where it is localized fixes the lookup, the cached name that the inbox rows read, and the conversation header. So MergeLocalMetadata goes back to writing what it was given, and FindGeneralConvFromTeamID goes back to a single query. Their tests go with them, replaced by one that purges a channel's METADATA body the way the server does and requires the default conv to come back as #general while an ordinary channel keeps an empty name rather than getting one invented for it.
chrisnojima
force-pushed
the
nojima/HOTPOT-emoji-cut-2
branch
from
August 13, 2026 20:42
1126191 to
248a3e8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A conv only ever learns its topic name from its max
METADATAmessage (localizer.go:883). A delete-history purges that message's body, so the message survives as valid-but-empty, the type switch matches nothing, and the conv localizes with no name.Two symptoms, one cause:
FindGeneralConvFromTeamIDlooks#generalup by topic name, and a nameless conv can never match. Same for the bot install modal, which resolves its conversation the same way.Evidence
Reproduced against a real team whose
#generalhad a delete-history run over it years ago. Instrumenting the localizer to dump each max msg it walks:The header still says
METADATA; the body unboxes toNONE, becauseboxer.go:955treats an emptyBodyCiphertextas a deleted body. The message that superseded it is adeletehistory.So the purge is server-side data: every member of that team localizes the conv nameless, not just a machine with a stale cache. A healthy team's
#generallocalizes to"general"on the same build, which is the control for the harness.Fix
One hunk, in the localizer: if a conv localizes with no topic name and is marked
IsDefaultConv, it is#generalwhatever its messages say.That is deliberately the only change. An earlier revision of this PR fixed it at two other sites —
MergeLocalMetadataandFindGeneralConvFromTeamID— and measuring that version showed why neither belongs:Info.TopicName(inboxsource.go:60), not on the cached copy. Repairing the cache could never make the#generallookup hit; every call still fell through to theIsDefaultConvretry.Naming the conv where it is localized fixes the lookup, the cached name, and the header at once, so both of those hunks are reverted here.
Scope: this covers a conv marked
IsDefaultConv. A non-default channel whoseMETADATAwas purged still localizes nameless, deliberately — its real name is genuinely unrecoverable, so nothing should be invented for it.Not a regression — this code path is unchanged since v6.6.3, and the topic-name filter since 2020.
Testing
Verified live: services built from
masterand from this branch, run in turn against the same account, with the conv's inbox cache row deleted between runs so it had to re-localize from the server.Info.TopicName""general""generalFindGeneralConvFromTeamIDERROR: Not foundchannel="general"Also checked the upgrade path, where the cache already holds the empty name an older client wrote. A
masterbuild was left to poison the cached copy (Not found, blank tab), then the branch build was swapped in without clearing the cache: the lookup resolved asgeneraland the Emoji tab rendered. So no cache clear or re-sync is needed on upgrade — the cached empty name is not what the lookup filters on, since that query goes remote and localizes.Unit test added but not run locally — every test in
chatandchat/storagesigns up a fake user against a dev API server onlocalhost:3000, so the whole package fails at signup on a dev machine. CI is its first real run.TestChatSrvPurgedMetadataDefaultsTopicName— blanks a channel'sMETADATABodyCiphertextthe way a server-side purge does, re-localizes, and requires the team's default conv to come back as#generalwhile an ordinary channel keeps an empty name rather than getting one invented for it. It then runs the#generallookup that the emoji list and the bot install modal wait on. It pokesIsDefaultConvonto the mock conv, sincekbtestnever sets that flag on creation; if this goes red, that's the line to look at.🤖 Generated with Claude Code