slopus/happy · warning · Error
Agent state version mismatch
Error message
Agent state version mismatch
What it means
Thrown in ApiSessionClient's agent-state update path when the server responds 'version-mismatch'. The client adopts the server's newer agentStateVersion and agentState (decrypted, or null if absent), then throws to trigger a retry of the state push with the fresh version. This is the same optimistic-concurrency pattern used for metadata.
Source
Thrown at packages/happy-cli/src/api/apiSession.ts:975
* Update session agent state
* @param handler - Handler function that returns the updated agent state
*/
updateAgentState(handler: (metadata: AgentState) => AgentState) {
logger.debugLargeJson('Updating agent state', this.agentState);
this.agentStateLock.inLock(async () => {
await backoff(async () => {
let updated = handler(this.agentState || {});
const answer = await this.socket.emitWithAck('update-state', { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
if (answer.result === 'success') {
this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
this.agentStateVersion = answer.version;
logger.debug('Agent state updated', this.agentState);
} else if (answer.result === 'version-mismatch') {
if (answer.version > this.agentStateVersion) {
this.agentStateVersion = answer.version;
this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
}
throw new Error('Agent state version mismatch');
} else if (answer.result === 'error') {
// console.error('Agent state update error', answer);
// Hard error - ignore
}
});
});
}
/**
* Wait for socket buffer to flush
*/
async flush(): Promise<void> {
await Promise.race([
this.sendSync.invalidateAndAwait(),
delay(10000)
]);
if (!this.socket.connected) {
return;View on GitHub (pinned to b824cd0a46)
Solutions
- Let the retry loop resend — the client already adopted the server's newer agent state
- Ensure only one writer drives the session's agent state at a time
- If persistent, re-pull session state from the server before pushing updates
Defensive patterns
Strategy: retry
Try / catch
try {
await updateAgentState(sessionId, agentState);
} catch (e) {
if (e.message === 'Agent state version mismatch') {
// server state already merged locally; resend converges
await updateAgentState(sessionId, agentState);
} else throw e;
} Prevention
- Drive agent state from a single client per session
- Treat version-mismatch as a normal conflict and retry
- Re-pull agent state after reconnecting a dropped session
When it happens
Trigger: Agent state is updated concurrently from another client attached to the same session (e.g., a second CLI instance or the mobile app), so the local agentStateVersion is behind the server's.
Common situations: Two machines driving one session; app reconnecting while a stale update loop from the previous connection retries; rapid successive agent-state changes racing.
Related errors
- Metadata version mismatch
- Metadata version mismatch
- Daemon state version mismatch
- Session not started
- ACP session is not started
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/b6a73e9d38169f84.
Report an issue: GitHub.