-
Notifications
You must be signed in to change notification settings - Fork 29
fix(bridge): report a GOODBYE'd call as unknown-outcome, not failed #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,11 @@ | |
| * rewrite the contract-owned message while doing so. | ||
| */ | ||
|
|
||
| import { isConsumerReconnectTransient, StaleRouteHandleError } from "@cortexkit/subc-client"; | ||
| import { | ||
| isConsumerReconnectTransient, | ||
| StaleRouteHandleError, | ||
| SubcError, | ||
| } from "@cortexkit/subc-client"; | ||
|
|
||
| import { isBridgeTransportTimeout } from "./bridge.js"; | ||
| import { SubcRootGenerationExpiredError, SubcRootReapedError } from "./subc-transport.js"; | ||
|
|
@@ -52,6 +56,35 @@ export function toolErrorFromResponse( | |
| export const BASH_TRANSPORT_DISPOSITION = | ||
| "The transport to the AFT daemon was interrupted; no background task was created for this command and no task ID exists. Re-run the command. Do not poll bash_status for it."; | ||
|
|
||
| /** | ||
| * Agent-facing guidance for a call the daemon GOODBYE'd mid-flight. | ||
| * | ||
| * Deliberately does NOT say the call failed. The daemon emits route GOODBYEs | ||
| * after its drain wait regardless of whether that drain completed, so a call | ||
| * in flight at GOODBYE was admitted BEFORE the gate closed and may already | ||
| * have run to completion with only its reply lost. "Failed" reads as an | ||
| * invitation to re-run, which double-applies a mutation that already landed. | ||
| */ | ||
| export const SUBC_MODULE_RESTART_DISPOSITION = | ||
| "The AFT daemon module restarted while this call was in flight, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation."; | ||
|
|
||
| /** | ||
| * A route GOODBYE delivered against an in-flight request. | ||
| * | ||
| * COUPLING: subc-client raises this as a bare `SubcError` carrying no code | ||
| * (client.ts, the `FrameType.Goodbye` branch), so the message literal is the | ||
| * only discriminator available. Asked upstream for a stable `code` on that | ||
| * error; until it exists this match is the seam and will fail open (no | ||
| * disposition appended) rather than misclassify. | ||
| */ | ||
| function isRouteGoodbyeError(error: unknown): boolean { | ||
| return ( | ||
| error instanceof SubcError && | ||
| error.code === undefined && | ||
| error.message.includes("route closed by subc") | ||
| ); | ||
| } | ||
|
|
||
| function isTransportClassError(error: unknown): boolean { | ||
| return ( | ||
| isBridgeTransportTimeout(error) || | ||
|
|
@@ -67,8 +100,20 @@ function isTransportClassError(error: unknown): boolean { | |
| * object, class, code, or retry behavior. Other commands retain their errors. | ||
| */ | ||
| export function adaptToolError(command: string, error: unknown): unknown { | ||
| if (command !== "bash" || !isTransportClassError(error)) return error; | ||
| if (!(error instanceof Error)) return error; | ||
|
|
||
| // Checked before the bash branch, and applied to every command: a GOODBYE'd | ||
| // call has an unknown outcome, so BASH_TRANSPORT_DISPOSITION's "no task was | ||
| // created, re-run the command" would be actively wrong here. | ||
| if (isRouteGoodbyeError(error)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The new GOODBYE branch appends the disposition message to the error for every command, but function docstring still claims 'Other commands retain their errors' and the file header says hosts must not rewrite the contract-owned message. That contract text is now stale: a non-bash GOODBYE error is no longer returned unchanged. The behavior is intentional per the PR, so this is mainly a documentation/contract-consistency concern — update the docstring to call out the GOODBYE exception so future readers don't assume non-bash errors are always passed through untouched. Prompt for AI agents |
||
| if (error.message.includes(SUBC_MODULE_RESTART_DISPOSITION)) return error; | ||
| error.message = error.message | ||
| ? `${error.message} ${SUBC_MODULE_RESTART_DISPOSITION}` | ||
| : SUBC_MODULE_RESTART_DISPOSITION; | ||
| return error; | ||
| } | ||
|
|
||
| if (command !== "bash" || !isTransportClassError(error)) return error; | ||
| if (error.message.includes(BASH_TRANSPORT_DISPOSITION)) return error; | ||
| error.message = error.message | ||
| ? `${error.message} ${BASH_TRANSPORT_DISPOSITION}` | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The GOODBYE detection rests on a fragile substring match:
error.message.includes("route closed by subc")combined witherror.code === undefined. This is the exact coupling the PR flags as a known limitation, and the downside of a false negative is material — the UNKNOWN/verify-before-rerun guidance would simply not be appended, recreating the blind re-run of a possibly-landed mutation that this change exists to prevent. Since the literal is the only discriminator until upstream adds a code, consider extracting it to a single named constant (shared by the matcher and the test helper) and adding a comment marking it as a temporary upstream-coupling to revisit, so a future wire-message change fails loudly in tests rather than silently. Note the check is a partialincludes, so it also cannot distinguish a route GOODBYE from any other SubcError that happens to contain the phrase.Prompt for AI agents