Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix(web-shell): restore the Managed approval retry budget on reload
Since R1-1, a page Refresh drops the Session summary, so the Actions reader
sees enabled === undefined and returns early, never reaching the branch that
resets loadFailures. After the retry ladder had been spent, the reload's
first failed read scheduled no retry and the card stayed away until the next
event, even though the service was healthy again.

Reset the budget in the unknown-capability branch too. Only a reload, a user
action, resets it, so the ladder stays bounded.

Candidate patch from wenshao's round-5 real-stack verification of #13107.
  • Loading branch information
yiliang114 committed Oct 3, 2026
commit a52c54f56fc173d0672ae33e6691166df20fc46a
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,36 @@ describe('useManagedActions', () => {
expect(hook.latest?.loadError).toBeUndefined();
});

it('restores the retry budget after a reload of the Session summary', async () => {
vi.useFakeTimers();
const listPending = vi
.fn()
.mockRejectedValueOnce(new Error('unavailable'))
.mockRejectedValueOnce(new Error('unavailable'))
.mockRejectedValueOnce(new Error('unavailable'))
.mockRejectedValueOnce(new Error('unavailable'))
.mockRejectedValueOnce(new Error('unavailable'))
.mockResolvedValueOnce([pending]);
const provider = {
actions: { listPending, respond: vi.fn() },
} as unknown as ManagedAgentProvider;
const hook = mount(provider, { enabled: true, events: [] });
for (const delay of [0, 2_000, 5_000, 10_000, 60_000]) {
await act(async () => vi.advanceTimersByTimeAsync(delay));
}
expect(listPending).toHaveBeenCalledTimes(4);

// Refresh reloads the summary: the capability is unknown, then known again.
hook.rerender({ enabled: undefined });
hook.rerender({ enabled: true });
await act(async () => vi.advanceTimersByTimeAsync(0));
expect(listPending).toHaveBeenCalledTimes(5);
await act(async () => vi.advanceTimersByTimeAsync(2_000));
expect(listPending).toHaveBeenCalledTimes(6);
expect(hook.latest?.action).toEqual(pending);
expect(hook.latest?.loadError).toBeUndefined();
});

it('keeps the shown approval while the capability is unknown', async () => {
const listPending = vi.fn().mockResolvedValue([pending]);
const respond = vi.fn().mockResolvedValue(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ export function useManagedActions(
loadFailures.current = 0;
return undefined;
}
if (enabled === undefined) return undefined;
if (enabled === undefined) {
// So is a reload, which is how a user retries from the page: the
Comment thread
yiliang114 marked this conversation as resolved.
// reader comes back with the same Session and a fresh retry budget.
loadFailures.current = 0;
return undefined;
}
const abort = new AbortController();
let retryTimer: ReturnType<typeof setTimeout> | undefined;
reader
Expand Down