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

  1. Let the retry loop resend — the client already adopted the server's newer agent state
  2. Ensure only one writer drives the session's agent state at a time
  3. 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

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


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/b6a73e9d38169f84. Report an issue: GitHub.