fix(bridge): reset claude session when OpenClaw sends no assistant history
The May 7 fix made the bridge detect /new turns by scanning messages
for the bare-reset marker ("A new session was started via /new or
/reset"). That handles the case where /new is the body of the
current user turn, but misses a very common path: the user types
`/new` as a standalone slash command. OpenClaw processes those in a
side lane (e.g. agent:<id>:discord:slash:<chat>) that doesn't go
through the bridge — it just renames the old session file aside.
The follow-up real message then lands on a brand-new OpenClaw
session, but as a normal turn with `softResetTriggered=false`,
non-empty body, not bare /new — so isBareSessionReset is false in
OpenClaw (get-reply isBareSessionReset condition) and the marker is
never injected. The bridge keeps resuming the long-stale
claudeSessionId from before the reset.
OpenClaw always sends the full conversation history each turn
(system + user/assistant pairs + latest user). A request with zero
assistant turns in messages[] is therefore a positive signal that
the OpenClaw session is brand-new and any prior claudeSessionId we
hold belongs to an abandoned OpenClaw session.
Treat "no assistant history" as equivalent to bareSessionReset:
removeSession + existingEntry = null, so dispatchToClaude is called
without --resume and claude starts a fresh CLI session whose id we
then store. Also covers any future OpenClaw reset path that resets
the session without injecting the marker (idle timeout new-session,
admin tooling, etc.).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -276,9 +276,37 @@ export function createBridgeServer(config: BridgeServerConfig): http.Server {
|
||||
// updated `claudeSessionId` and we want to resume into the latest
|
||||
// one rather than a stale snapshot from request-arrival time.
|
||||
existingEntry = sessionKey ? getSession(workspace, sessionKey) : null;
|
||||
if (bareSessionReset && existingEntry && sessionKey) {
|
||||
|
||||
// Detect a fresh OpenClaw session even when the bare-reset marker is
|
||||
// absent. The marker only arrives when `/new` is the body of *this*
|
||||
// user turn (see get-reply isBareSessionReset). When `/new` is sent
|
||||
// as a separate slash command (e.g. via Discord's slash UI), OpenClaw
|
||||
// processes the reset in a side lane that doesn't hit the bridge —
|
||||
// it just renames the prior session file aside. The follow-up real
|
||||
// message then arrives on a brand-new OpenClaw session, but as a
|
||||
// normal turn with no marker. Without this check, the bridge happily
|
||||
// resumes the long-stale claudeSessionId from before the reset.
|
||||
//
|
||||
// OpenClaw sends the full conversation history every turn (system +
|
||||
// user/assistant pairs + latest user). A request with zero assistant
|
||||
// turns is therefore a positive signal that the OpenClaw session is
|
||||
// brand-new and any prior claudeSessionId we hold is from a previous
|
||||
// OpenClaw session that the user already abandoned.
|
||||
const hasAssistantHistory = body.messages.some((m) => {
|
||||
if (m.role !== "assistant") return false;
|
||||
if (typeof m.content === "string") return m.content.trim().length > 0;
|
||||
return m.content.some(
|
||||
(c) => c.type === "text" && (c.text ?? "").trim().length > 0,
|
||||
);
|
||||
});
|
||||
const isFreshOpenClawSession = !hasAssistantHistory;
|
||||
|
||||
if ((bareSessionReset || isFreshOpenClawSession) && existingEntry && sessionKey) {
|
||||
const reason = bareSessionReset
|
||||
? "bare /new detected"
|
||||
: "fresh OpenClaw session (no assistant history in messages[])";
|
||||
logger.info(
|
||||
`[contractor-bridge] bare /new detected — dropping prior CLI session sessionKey=${sessionKey} prevClaudeSessionId=${existingEntry.claudeSessionId}`,
|
||||
`[contractor-bridge] ${reason} — dropping prior CLI session sessionKey=${sessionKey} prevClaudeSessionId=${existingEntry.claudeSessionId}`,
|
||||
);
|
||||
removeSession(workspace, sessionKey);
|
||||
existingEntry = null;
|
||||
|
||||
Reference in New Issue
Block a user