fix(plugins): bound the exit flush, and stop openclaw deleting a legacy account
Second review round. The first two are regressions from the first round. The forced exit flush added fifteen seconds to host shutdown. Node re-emits beforeExit whenever the handler schedules async work, so an unconditional flush(true) looped until the five-attempt budget was spent, and against the real 3s delivery timeout that is 15s added to the shutdown of whatever editor or CLI is hosting this. The backoff used to end that loop after one attempt; removing it for the forced path removed the only thing bounding it. Measured at 15008ms, now 6001ms with a one-shot latch, and 12ms when delivery is healthy, which is the only case most people ever see. openclaw deleted a legacy account before it had anything to replace it with. An install predating keyFingerprint has an email and no fingerprint, so the comparison failed and clearResolvedAccount() ran immediately; if the re-resolve then failed because the user was offline the email was gone from disk for good, and the per-key latch was already set so nothing retried. Now cleared only when a real fingerprint disagrees, which is the same trade the Python core makes and documents: verify, and keep what you have until the verification succeeds. The latch is released on a failed lookup so the next capture tries again. The overflow test did not exercise the path it is named for: with only two events captured the re-queue had an empty queue to merge into, so the slice on the failure path never ran, and that is the half deciding which end gets dropped. It now fills past the cap before flushing and asserts the exact surviving order. core 36, openclaw 432, opencode 35, deepseek 47. Wire e2e 9 of 9 and identity e2e 7 of 7 still pass against a real local server. Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
This commit is contained in:
@@ -58,7 +58,15 @@ function distinctId(apiKey?: string): string {
|
||||
// apiKey guard the comparison is `undefined === ""` for any call that
|
||||
// simply omits the key, so a capture with no context wiped a perfectly
|
||||
// good account out of openclaw.json.
|
||||
if (apiKey) clearResolvedAccount();
|
||||
//
|
||||
// A row with an email and NO fingerprint is the legacy shape, from an
|
||||
// install predating this field. Clearing it here deleted a real account
|
||||
// before anything had replaced it, and if the re-resolve then failed
|
||||
// because the user was offline the email was gone from disk for good. The
|
||||
// Python core refuses the same trade: verify, and keep what you have until
|
||||
// the verification succeeds. resolveEmail below overwrites both fields
|
||||
// when it does, so there is nothing to clear first.
|
||||
if (apiKey && auth.keyFingerprint) clearResolvedAccount();
|
||||
}
|
||||
} catch {
|
||||
// Fall through to the API key or anonymous identity.
|
||||
@@ -94,6 +102,11 @@ function resolveEmail(apiKey: string): void {
|
||||
const fingerprint = keyFingerprint(apiKey);
|
||||
if (resolutionAttemptedFor === fingerprint) return;
|
||||
resolutionAttemptedFor = fingerprint;
|
||||
const releaseLatch = () => {
|
||||
// A failed lookup must not pin the fallback identity for the rest of the
|
||||
// process. Released so the next capture tries again.
|
||||
if (resolutionAttemptedFor === fingerprint) resolutionAttemptedFor = "";
|
||||
};
|
||||
fetch(`${getBaseUrl().replace(/\/+$/, "")}/v1/ping/`, {
|
||||
method: "GET",
|
||||
headers: { Authorization: `Token ${apiKey}`, "Content-Type": "application/json" },
|
||||
@@ -110,7 +123,8 @@ function resolveEmail(apiKey: string): void {
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// The API-key hash remains a stable fallback.
|
||||
// The API-key hash remains a stable fallback, and the next capture retries.
|
||||
releaseLatch();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user