fix(persistence): bump generation before durable SSH writes

This commit is contained in:
Neil
2026-08-23 19:40:42 -07:00
parent ab1dbaa8bd
commit 17984c56ff
4 changed files with 15 additions and 0 deletions
@@ -7,6 +7,7 @@ import { normalizeSshPtyConsumerRecovery } from './ssh-normalization'
export type SshPtyConsumerRecoveryOperations = {
state: StoreOwnedPersistedState
protectedSecrets: Pick<ProtectedSecretPersistence, 'isSealed' | 'removeRetainedBlob'>
scheduleSave: () => void
flushDurableStateOrThrowAsync: () => Promise<void>
}
@@ -51,6 +52,7 @@ export async function upsertSshPtyConsumerRecovery(
...recoveries.filter((candidate) => candidate.targetId !== normalized.targetId),
normalized
]
operations.scheduleSave()
await flushSshPtyConsumerRecovery(operations)
}
@@ -65,5 +67,6 @@ export async function removeSshPtyConsumerRecovery(
}
operations.state.sshPtyConsumerRecoveries = next
operations.protectedSecrets.removeRetainedBlob(sshPtyOwnerLeaseSecretSlot(targetId))
operations.scheduleSave()
await flushSshPtyConsumerRecovery(operations)
}
@@ -8,6 +8,7 @@ export type SshPtyLeaseOperations = {
toStoredPtyId: (targetId: string, ptyId: string) => string
clearBindingsForTarget: (targetId: string) => void
clearBindingsForLeases: (targetId: string, leases: SshRemotePtyLease[]) => boolean
scheduleSave: () => void
flush: () => void
flushDurableStateOrThrowAsync: () => Promise<void>
}
@@ -54,6 +55,7 @@ export function upsertSshRemotePtyLease(
} else {
operations.state.sshRemotePtyLeases.push(next)
}
operations.scheduleSave()
operations.flush()
}
@@ -104,6 +106,7 @@ export function markSshRemotePtyLeases(
state: SshRemotePtyLease['state']
): void {
if (updateSshRemotePtyLeaseStates(operations, targetId, state)) {
operations.scheduleSave()
operations.flush()
}
}
@@ -125,6 +128,7 @@ export async function markSshRemotePtyLeasesAsync(
state: SshRemotePtyLease['state']
): Promise<void> {
if (updateSshRemotePtyLeaseStates(operations, targetId, state)) {
operations.scheduleSave()
await operations.flushDurableStateOrThrowAsync()
}
}
@@ -136,6 +140,7 @@ export async function markSshRemotePtyLeasesAttachedAsync(
): Promise<void> {
const relayPtyIds = new Set(ptyIds.map((ptyId) => operations.toStoredPtyId(targetId, ptyId)))
if (updateSshRemotePtyLeaseStates(operations, targetId, 'attached', relayPtyIds)) {
operations.scheduleSave()
await operations.flushDurableStateOrThrowAsync()
}
}
@@ -156,6 +161,7 @@ export function markSshRemotePtyLease(
const shouldClearBindings = state === 'terminated' || state === 'expired'
if (lease.state === state) {
if (shouldClearBindings && operations.clearBindingsForLeases(targetId, [lease])) {
operations.scheduleSave()
operations.flush()
}
return
@@ -171,6 +177,7 @@ export function markSshRemotePtyLease(
if (shouldClearBindings) {
operations.clearBindingsForLeases(targetId, [lease])
}
operations.scheduleSave()
operations.flush()
}
@@ -189,6 +196,7 @@ export function removeSshRemotePtyLease(
(lease) => lease.targetId !== targetId || lease.ptyId !== relayPtyId
)
if (operations.state.sshRemotePtyLeases.length !== before) {
operations.scheduleSave()
operations.flush()
}
}
@@ -204,6 +212,7 @@ export function removeSshRemotePtyLeases(
(lease) => lease.targetId !== targetId
)
if (operations.state.sshRemotePtyLeases.length !== before) {
operations.scheduleSave()
operations.flush()
}
}
@@ -107,6 +107,7 @@ export function addClaudeLivePtySessionId(
operations.state.claudeLivePtySessionIds = [...ids, sessionId].slice(
-MAX_CLAUDE_LIVE_PTY_SESSION_IDS
)
operations.scheduleSave()
// Why: flush sync so a force-quit right after a Claude spawn still seeds the live-PTY gate next launch.
operations.flush()
}
@@ -3856,6 +3856,7 @@ export class Store {
return {
state: this.state,
protectedSecrets: this.protectedSecrets,
scheduleSave: () => this.scheduleSave(),
flushDurableStateOrThrowAsync: () => this.flushDurableStateOrThrowAsync()
}
}
@@ -3909,6 +3910,7 @@ export class Store {
targetId,
leases
),
scheduleSave: () => this.scheduleSave(),
flush: () => this.flush(),
flushDurableStateOrThrowAsync: () => this.flushDurableStateOrThrowAsync()
}