mirror of https://github.com/openclaw/openclaw.git
fix(feishu): clear stale streamingStartPromise on card creation failure
Fixes #43322 * fix(feishu): clear stale streamingStartPromise on card creation failure When FeishuStreamingSession.start() throws (HTTP 400), the catch block sets streaming = null but leaves streamingStartPromise dangling. The guard in startStreaming() checks streamingStartPromise first, so all future deliver() calls silently skip streaming - the session locks permanently. Clear streamingStartPromise in the catch block so subsequent messages can retry streaming instead of dropping all future replies. Fixes #43322 * test(feishu): wrap push override in try/finally for cleanup safety
This commit is contained in:
parent
d9bc1920ed
commit
c6e32835d4
|
|
@ -323,6 +323,7 @@ Docs: https://docs.openclaw.ai
|
||||||
- Agents/failover: classify HTTP 422 malformed-request responses as `format` and recognize OpenRouter "requires more credits" billing errors so provider fallback triggers instead of surfacing raw errors. (#43823) thanks @jnMetaCode.
|
- Agents/failover: classify HTTP 422 malformed-request responses as `format` and recognize OpenRouter "requires more credits" billing errors so provider fallback triggers instead of surfacing raw errors. (#43823) thanks @jnMetaCode.
|
||||||
- Memory/QMD Windows: fail closed when `qmd.cmd` or `mcporter.cmd` wrappers cannot be resolved to a direct entrypoint, so memory search no longer falls back to shell execution on Windows.
|
- Memory/QMD Windows: fail closed when `qmd.cmd` or `mcporter.cmd` wrappers cannot be resolved to a direct entrypoint, so memory search no longer falls back to shell execution on Windows.
|
||||||
- macOS/remote gateway: stop PortGuardian from killing Docker Desktop and other external listeners on the gateway port in remote mode, so containerized and tunneled gateway setups no longer lose their port-forward owner on app startup. (#6755) Thanks @teslamint.
|
- macOS/remote gateway: stop PortGuardian from killing Docker Desktop and other external listeners on the gateway port in remote mode, so containerized and tunneled gateway setups no longer lose their port-forward owner on app startup. (#6755) Thanks @teslamint.
|
||||||
|
- Feishu/streaming recovery: clear stale `streamingStartPromise` when card creation fails (HTTP 400) so subsequent messages can retry streaming instead of silently dropping all future replies. Fixes #43322.
|
||||||
|
|
||||||
## 2026.3.8
|
## 2026.3.8
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -510,4 +510,50 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("recovers streaming after start() throws (HTTP 400)", async () => {
|
||||||
|
const errorMock = vi.fn();
|
||||||
|
let shouldFailStart = true;
|
||||||
|
|
||||||
|
// Intercept streaming instance creation to make first start() reject
|
||||||
|
const origPush = streamingInstances.push;
|
||||||
|
streamingInstances.push = function (this: any[], ...args: any[]) {
|
||||||
|
if (shouldFailStart) {
|
||||||
|
args[0].start = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValue(new Error("Create card request failed with HTTP 400"));
|
||||||
|
shouldFailStart = false;
|
||||||
|
}
|
||||||
|
return origPush.apply(this, args);
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
createFeishuReplyDispatcher({
|
||||||
|
cfg: {} as never,
|
||||||
|
agentId: "agent",
|
||||||
|
runtime: { log: vi.fn(), error: errorMock } as never,
|
||||||
|
chatId: "oc_chat",
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
|
||||||
|
|
||||||
|
// First deliver with markdown triggers startStreaming - which will fail
|
||||||
|
await options.deliver({ text: "```ts\nconst x = 1\n```" }, { kind: "block" });
|
||||||
|
|
||||||
|
// Wait for the async error to propagate
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(errorMock).toHaveBeenCalledWith(expect.stringContaining("streaming start failed"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Second deliver should create a NEW streaming session (not stuck)
|
||||||
|
await options.deliver({ text: "```ts\nconst y = 2\n```" }, { kind: "final" });
|
||||||
|
|
||||||
|
// Two instances created: first failed, second succeeded and closed
|
||||||
|
expect(streamingInstances).toHaveLength(2);
|
||||||
|
expect(streamingInstances[1].start).toHaveBeenCalled();
|
||||||
|
expect(streamingInstances[1].close).toHaveBeenCalled();
|
||||||
|
} finally {
|
||||||
|
streamingInstances.push = origPush;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,7 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
params.runtime.error?.(`feishu: streaming start failed: ${String(error)}`);
|
params.runtime.error?.(`feishu: streaming start failed: ${String(error)}`);
|
||||||
streaming = null;
|
streaming = null;
|
||||||
|
streamingStartPromise = null; // allow retry on next deliver
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue