slopus/happy · warning · Error
Metadata version mismatch
Error message
Metadata version mismatch
What it means
Thrown by updateMachineMetadata when the server responds 'version-mismatch' for machine metadata. Before throwing, the client adopts the server's newer version and metadata (if answer.version is higher) so the next attempt converges; the throw itself is deliberate — it signals the optimistic update conflicted and triggers the caller's retry logic.
Source
Thrown at packages/happy-cli/src/api/apiMachine.ts:394
await backoff(async () => {
const updated = handler(this.machine.metadata);
const answer = await this.socket.emitWithAck('machine-update-metadata', {
machineId: this.machine.id,
metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
expectedVersion: this.machine.metadataVersion
});
if (answer.result === 'success') {
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
this.machine.metadataVersion = answer.version;
logger.debug('[API MACHINE] Metadata updated successfully');
} else if (answer.result === 'version-mismatch') {
if (answer.version > this.machine.metadataVersion) {
this.machine.metadataVersion = answer.version;
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
}
throw new Error('Metadata version mismatch'); // Triggers retry
}
});
}
/**
* Update daemon state (runtime info) - similar to session updateAgentState
* Simplified without lock - relies on backoff for retry
*/
async updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void> {
await backoff(async () => {
const updated = handler(this.machine.daemonState);
const answer = await this.socket.emitWithAck('machine-update-state', {
machineId: this.machine.id,
daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
expectedVersion: this.machine.daemonStateVersion
});
View on GitHub (pinned to b824cd0a46)
Solutions
- Let the retry logic re-run updateMachineMetadata — the client already adopted the server's newer metadata
- Ensure only one daemon instance per machine is running
- If mismatches persist, refresh machine state from the server and reapply local edits
Defensive patterns
Strategy: retry
Try / catch
try {
await updateMachineMetadata(metadata);
} catch (e) {
if (e.message === 'Metadata version mismatch') {
// client already adopted server version; safe to retry
await updateMachineMetadata(metadata);
} else throw e;
} Prevention
- Run only one daemon per machine
- Serialize metadata writes through a single owner
- Treat this error as expected conflict noise, not a crash
When it happens
Trigger: Two writers (e.g., this daemon and another instance/machine process) update machine metadata concurrently; the local metadataVersion is stale relative to the server's.
Common situations: Running multiple daemons for the same machine; a daemon restarted while a stale keep-alive loop from the previous run was still retrying; rapid successive metadata updates racing.
Related errors
- Daemon state version mismatch
- Metadata version mismatch
- Agent state version mismatch
- Happy session ${session.id} is missing its Codex thread ID.
- Happy session ${session.id} is missing its Claude session ID
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/1c4475d46a7bd932.
Report an issue: GitHub.