{"record":{"id":"c61b0bb9c3ae9076","repo":"wavetermdev/waveterm","slug":"client-id-mismatch-expected-s-got-s","errorCode":null,"errorMessage":"client id mismatch: expected %s, got %s","messagePattern":"client id mismatch: expected (.+?), got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tsunami/engine/clientimpl.go","lineNumber":137,"sourceCode":"}\n\nfunc (c *ClientImpl) GetIsDone() bool {\n\tc.Lock.Lock()\n\tdefer c.Lock.Unlock()\n\treturn c.IsDone\n}\n\nfunc (c *ClientImpl) checkClientId(clientId string) error {\n\tif clientId == \"\" {\n\t\treturn fmt.Errorf(\"client id cannot be empty\")\n\t}\n\tc.Lock.Lock()\n\tdefer c.Lock.Unlock()\n\tif c.CurrentClientId == \"\" || c.CurrentClientId == clientId {\n\t\tc.CurrentClientId = clientId\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"client id mismatch: expected %s, got %s\", c.CurrentClientId, clientId)\n}\n\nfunc (c *ClientImpl) clientTakeover(clientId string) {\n\tc.Lock.Lock()\n\tdefer c.Lock.Unlock()\n\tc.CurrentClientId = clientId\n}\n\nfunc (c *ClientImpl) doShutdown(reason string) {\n\tc.Lock.Lock()\n\tdefer c.Lock.Unlock()\n\tif c.IsDone {\n\t\treturn\n\t}\n\tc.DoneReason = reason\n\tc.IsDone = true\n\tclose(c.DoneCh)\n}","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/tsunami/engine/clientimpl.go#L119-L155","documentation":"checkClientId enforces single-client semantics: once CurrentClientId is set, calls with a different id are rejected with this mismatch error. This detects a second client (or a restarted client with a new id) trying to act on the same ClientImpl.","triggerScenarios":"Calling a client method with clientId X while CurrentClientId is Y (already claimed by a previous call). The claim is set on first successful checkClientId and only changed via clientTakeover.","commonSituations":"Reconnecting after a crash with a newly generated id while the old ClientImpl is still in use; two goroutines/processes sharing one client with different ids; stale cached id from a previous session.","solutions":["Reuse the same clientId consistently for the lifetime of the ClientImpl (store it once at first successful call)","Call clientTakeover deliberately when a new client should replace the old one","On mismatch, log both ids and reinitialize or restart the client cleanly"],"exampleFix":"// before\nclient.Do(newRandomUUID(), req) // new id every call\n// after\nif myClientID == \"\" { myClientID = newRandomUUID() }\nclient.Do(myClientID, req)","handlingStrategy":"validation","validationCode":"var myClientID string\nfunc ensureClientID(c *tsunami.ClientImpl, id string) (string, error) {\n    if myClientID == \"\" { myClientID = id }\n    if id != myClientID {\n        return \"\", fmt.Errorf(\"must use client id %s, got %s\", myClientID, id)\n    }\n    return myClientID, nil\n}","typeGuard":null,"tryCatchPattern":"if err := client.Send(id, msg); err != nil {\n    if strings.Contains(err.Error(), \"client id mismatch\") {\n        log.Printf(\"stale id %q; reinitializing client session\", id)\n        client = reinitClient()\n    }\n}","preventionTips":["Generate the id once and reuse it for the client's lifetime","Persist the id across reconnects instead of regenerating","Use clientTakeover explicitly when a replacement is intended"],"tags":["go","client","concurrency","identity"],"backgroundTag":"client-id-mismatch","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}