mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(server): mark Claude terminals as needing input when Claude goes idle (#6022)
Claude Code reports the kind of notification in `notification_type`, but the hook parser only read `matcher` and `reason`, which Claude never sends in Notification input. Every idle_prompt notification resolved to nothing, so a Claude terminal waiting for the user never showed as needing input. Closes #5845 Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -51,7 +51,7 @@ Claude hook mapping:
|
|||||||
|
|
||||||
- `UserPromptSubmit` → `running`
|
- `UserPromptSubmit` → `running`
|
||||||
- `Stop`, `StopFailure`, `SessionEnd` → `idle`
|
- `Stop`, `StopFailure`, `SessionEnd` → `idle`
|
||||||
- `Notification` with `reason` or `matcher` equal to `idle_prompt` → `needs-input`
|
- `Notification` with `notification_type` equal to `idle_prompt` → `needs-input`
|
||||||
|
|
||||||
Claude does not run `Stop` when the user interrupts a turn. A standalone Ctrl-C or Escape input
|
Claude does not run `Stop` when the user interrupts a turn. A standalone Ctrl-C or Escape input
|
||||||
while terminal activity is working clears the activity without finished attention. The same
|
while terminal activity is working clears the activity without finished attention. The same
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ function inputFrom(value: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function claudeNotification(notificationType: string) {
|
||||||
|
return JSON.stringify({
|
||||||
|
session_id: "session-1",
|
||||||
|
hook_event_name: "Notification",
|
||||||
|
message: "Claude is waiting for your input",
|
||||||
|
notification_type: notificationType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function ttyInput() {
|
function ttyInput() {
|
||||||
return {
|
return {
|
||||||
isTTY: true,
|
isTTY: true,
|
||||||
@@ -123,7 +132,7 @@ describe("runHooksCommand", () => {
|
|||||||
const send = await runHook(
|
const send = await runHook(
|
||||||
claudeProvider.id,
|
claudeProvider.id,
|
||||||
claudeProvider.events[4].event,
|
claudeProvider.events[4].event,
|
||||||
inputFrom('{"reason":"idle_prompt"}'),
|
inputFrom(claudeNotification("idle_prompt")),
|
||||||
);
|
);
|
||||||
|
|
||||||
expectPostedState(send, "needs-input");
|
expectPostedState(send, "needs-input");
|
||||||
@@ -131,11 +140,11 @@ describe("runHooksCommand", () => {
|
|||||||
|
|
||||||
it.each(["permission_prompt", "elicitation_prompt", "elicitation_response", "auth_success"])(
|
it.each(["permission_prompt", "elicitation_prompt", "elicitation_response", "auth_success"])(
|
||||||
"ignores Claude %s notifications",
|
"ignores Claude %s notifications",
|
||||||
async (reason) => {
|
async (notificationType) => {
|
||||||
const send = await runHook(
|
const send = await runHook(
|
||||||
claudeProvider.id,
|
claudeProvider.id,
|
||||||
claudeProvider.events[4].event,
|
claudeProvider.events[4].event,
|
||||||
inputFrom(JSON.stringify({ reason })),
|
inputFrom(claudeNotification(notificationType)),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(send.calls).toEqual([]);
|
expect(send.calls).toEqual([]);
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ function isIdlePrompt(raw: string | null): boolean {
|
|||||||
try {
|
try {
|
||||||
const notification = JSON.parse(raw) as unknown;
|
const notification = JSON.parse(raw) as unknown;
|
||||||
if (!notification || typeof notification !== "object") return false;
|
if (!notification || typeof notification !== "object") return false;
|
||||||
const payload = notification as { matcher?: unknown; reason?: unknown };
|
const payload = notification as { notification_type?: unknown };
|
||||||
return payload.matcher === "idle_prompt" || payload.reason === "idle_prompt";
|
return payload.notification_type === "idle_prompt";
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user