Skip to content

Commit 39caf2a

Browse files
omegent-app[bot]patroza
authored andcommitted
fix(discord-bot): put T3 thread id on short ops alerts; skip interrupt FATALS (#281)
FATAL/BRIDGE short Discord messages only showed the title; the thread id lived in the attachment. Put thread= (and channel=) on the short body. Also stop paging "T3 thread subscription exited" on interrupt-only exits (bot restart spam). Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com> Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
1 parent 4b4f60a commit 39caf2a

5 files changed

Lines changed: 92 additions & 11 deletions

File tree

apps/discord-bot/src/features/Alerts.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,26 @@ describe("Discord alert content", () => {
161161
expect(new TextDecoder().decode(delivery.files[0]?.data)).toBe(trace);
162162
}
163163
});
164+
165+
it("puts t3 thread id on the short fatal/bridge Discord message", () => {
166+
const trace = "stack goes in the attachment";
167+
const threadId = "44c2ab99-729b-4999-83cc-3a4dd04432e0";
168+
const channelId = "1532989230219919520";
169+
const fatal = fatalAlertDelivery("T3 thread subscription exited", trace, {
170+
threadId,
171+
channelId,
172+
});
173+
expect(fatal.content).toContain("**FATAL: T3 thread subscription exited**");
174+
expect(fatal.content).toContain(`thread=\`${threadId}\``);
175+
expect(fatal.content).toContain(`channel=\`${channelId}\``);
176+
expect(fatal.content).not.toContain(trace);
177+
expect(new TextDecoder().decode(fatal.files[0]?.data)).toBe(trace);
178+
179+
const bridge = bridgeAlertDelivery("Working heartbeat failed", trace, { threadId });
180+
expect(bridge.content).toContain("**BRIDGE: Working heartbeat failed**");
181+
expect(bridge.content).toContain(`thread=\`${threadId}\``);
182+
expect(bridge.content).not.toContain("channel=");
183+
});
164184
});
165185

166186
describe("session last_error alert classification", () => {

apps/discord-bot/src/features/Alerts.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -698,12 +698,46 @@ function alertTraceDelivery(content: string, filename: string, trace: string): A
698698
};
699699
}
700700

701-
export function fatalAlertDelivery(title: string, trace: string): AlertTraceDelivery {
702-
return alertTraceDelivery(`**FATAL: ${title}**`, "fatal-trace.txt", trace);
701+
/** Identity lines shown in the short Discord alert body (not only the attachment). */
702+
export type AlertIdentity = {
703+
readonly threadId?: string;
704+
readonly channelId?: string;
705+
};
706+
707+
function alertIdentityLines(identity?: AlertIdentity): ReadonlyArray<string> {
708+
if (identity === undefined) return [];
709+
const lines: string[] = [];
710+
if (identity.threadId !== undefined && identity.threadId.trim() !== "") {
711+
lines.push(`thread=\`${identity.threadId}\``);
712+
}
713+
if (identity.channelId !== undefined && identity.channelId.trim() !== "") {
714+
lines.push(`channel=\`${identity.channelId}\``);
715+
}
716+
return lines;
717+
}
718+
719+
export function fatalAlertDelivery(
720+
title: string,
721+
trace: string,
722+
identity?: AlertIdentity,
723+
): AlertTraceDelivery {
724+
return alertTraceDelivery(
725+
[`**FATAL: ${title}**`, ...alertIdentityLines(identity)].join("\n"),
726+
"fatal-trace.txt",
727+
trace,
728+
);
703729
}
704730

705-
export function bridgeAlertDelivery(title: string, trace: string): AlertTraceDelivery {
706-
return alertTraceDelivery(`**BRIDGE: ${title}**`, "bridge-trace.txt", trace);
731+
export function bridgeAlertDelivery(
732+
title: string,
733+
trace: string,
734+
identity?: AlertIdentity,
735+
): AlertTraceDelivery {
736+
return alertTraceDelivery(
737+
[`**BRIDGE: ${title}**`, ...alertIdentityLines(identity)].join("\n"),
738+
"bridge-trace.txt",
739+
trace,
740+
);
707741
}
708742

709743
export function sessionErrorAlertDelivery(threadId: string, trace: string): AlertTraceDelivery {
@@ -744,14 +778,19 @@ export function formatAlertCause(cause: unknown, maxLen?: number): string {
744778
* or channel unset. Does not require DiscordREST in the caller — uses the
745779
* watchdog-held poster.
746780
*/
747-
export const postFatalAlert = (key: string, title: string, detail: string) =>
781+
export const postFatalAlert = (
782+
key: string,
783+
title: string,
784+
detail: string,
785+
identity?: AlertIdentity,
786+
) =>
748787
Effect.gen(function* () {
749788
const p = poster;
750789
if (p === null) {
751-
yield* Effect.logError(`Fatal (no alerts channel): ${title}`, { detail });
790+
yield* Effect.logError(`Fatal (no alerts channel): ${title}`, { detail, ...identity });
752791
return;
753792
}
754-
const delivery = fatalAlertDelivery(title, detail);
793+
const delivery = fatalAlertDelivery(title, detail, identity);
755794
yield* p(`fatal:${key}`, delivery.content, FATAL_COOLDOWN_MS, delivery.files);
756795
});
757796

@@ -760,14 +799,19 @@ export const postFatalAlert = (key: string, title: string, detail: string) =>
760799
* stream/heartbeat Discord errors, and other bridge soft-failures that leave
761800
* Discord threads desynced while T3 still advances.
762801
*/
763-
export const postBridgeAlert = (key: string, title: string, detail: string) =>
802+
export const postBridgeAlert = (
803+
key: string,
804+
title: string,
805+
detail: string,
806+
identity?: AlertIdentity,
807+
) =>
764808
Effect.gen(function* () {
765809
const p = poster;
766810
if (p === null) {
767-
yield* Effect.logError(`Bridge alert (no alerts channel): ${title}`, { detail });
811+
yield* Effect.logError(`Bridge alert (no alerts channel): ${title}`, { detail, ...identity });
768812
return;
769813
}
770-
const delivery = bridgeAlertDelivery(title, detail);
814+
const delivery = bridgeAlertDelivery(title, detail, identity);
771815
yield* p(`bridge:${key}`, delivery.content, BRIDGE_ALERT_COOLDOWN_MS, delivery.files);
772816
});
773817

apps/discord-bot/src/features/BridgeHub.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ export const makeBridgeHub = (runBridge: BridgeRunner) =>
280280
`bridge:${input.discordChannelId}`,
281281
"Discord bridge fiber failed",
282282
`channel=\`${input.discordChannelId}\` thread=\`${input.t3ThreadId}\`\n${pretty}`,
283+
{ threadId: input.t3ThreadId, channelId: input.discordChannelId },
283284
);
284285
yield* Deferred.succeed(ready, undefined).pipe(Effect.ignore);
285286
}).pipe(Effect.asVoid),

apps/discord-bot/src/features/ResponseBridge.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
formatTurnResponseStatsLine,
1717
} from "@t3tools/shared/turnResponseStats";
1818
import { Discord, DiscordConfig, DiscordREST, UI } from "dfx";
19+
import * as Cause from "effect/Cause";
1920
import * as Deferred from "effect/Deferred";
2021
import * as Effect from "effect/Effect";
2122
import * as Fiber from "effect/Fiber";
@@ -4615,6 +4616,7 @@ export const runBridge = (
46154616
`thread=\`${input.t3ThreadId}\``,
46164617
formatAlertCause(cause),
46174618
].join("\n"),
4619+
{ threadId: input.t3ThreadId, channelId: input.discordChannelId },
46184620
);
46194621
}).pipe(Effect.asVoid),
46204622
),
@@ -4757,6 +4759,7 @@ export const runBridge = (
47574759
`phase=\`${phase}\``,
47584760
pretty,
47594761
].join("\n"),
4762+
{ threadId: input.t3ThreadId, channelId: input.discordChannelId },
47604763
);
47614764
}).pipe(Effect.asVoid);
47624765

@@ -5308,6 +5311,7 @@ export const runBridge = (
53085311
`failureCount=${failureCount}`,
53095312
pretty,
53105313
].join("\n"),
5314+
{ threadId: input.t3ThreadId, channelId: input.discordChannelId },
53115315
);
53125316
}
53135317
// Do not advance lastDeliveredSequence — delivery lag keeps HTTP reconcile on.
@@ -5433,6 +5437,7 @@ export const runBridge = (
54335437
`thread=\`${input.t3ThreadId}\``,
54345438
pretty,
54355439
].join("\n"),
5440+
{ threadId: input.t3ThreadId, channelId: input.discordChannelId },
54365441
);
54375442
}).pipe(Effect.asVoid),
54385443
),
@@ -5565,6 +5570,7 @@ export const runBridge = (
55655570
`mode=\`${mode}\``,
55665571
pretty,
55675572
].join("\n"),
5573+
{ threadId: input.t3ThreadId, channelId: input.discordChannelId },
55685574
);
55695575
}).pipe(Effect.asVoid),
55705576
),
@@ -5621,7 +5627,15 @@ export const runBridge = (
56215627
Effect.catchCause((cause) =>
56225628
Effect.gen(function* () {
56235629
// Follower retries internally; this is only if the outer effect is interrupted
5624-
// or fails without recovery.
5630+
// or fails without recovery. Bot restart / fiber cancel is interrupt-only —
5631+
// expected, high volume; do not page #omegent-alerts.
5632+
if (Cause.hasInterruptsOnly(cause)) {
5633+
yield* Effect.logInfo("Bridge subscribeThread interrupted (no alert)", {
5634+
discordChannelId: input.discordChannelId,
5635+
t3ThreadId: input.t3ThreadId,
5636+
});
5637+
return;
5638+
}
56255639
const pretty = formatAlertCause(cause);
56265640
yield* Effect.logError("Bridge subscribeThread exited", {
56275641
discordChannelId: input.discordChannelId,
@@ -5632,6 +5646,7 @@ export const runBridge = (
56325646
`subscribe:${input.t3ThreadId}`,
56335647
"T3 thread subscription exited",
56345648
`channel=\`${input.discordChannelId}\` thread=\`${input.t3ThreadId}\`\n${pretty}`,
5649+
{ threadId: input.t3ThreadId, channelId: input.discordChannelId },
56355650
);
56365651
}).pipe(Effect.asVoid),
56375652
),

apps/discord-bot/src/features/ThreadRestore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ export const rehydrateBridges = (source: "boot" | "reconnect") =>
254254
`rehydrate:${link.discordThreadId}`,
255255
"Bridge rehydrate failed",
256256
`source=\`${source}\` channel=\`${link.discordThreadId}\` thread=\`${link.t3ThreadId}\`\n${pretty}`,
257+
{ threadId: link.t3ThreadId, channelId: link.discordThreadId },
257258
);
258259
}).pipe(Effect.asVoid),
259260
),

0 commit comments

Comments
 (0)