Skip to content

Commit 7380638

Browse files
hugocasaclaude
andcommitted
fix: stale flag-name strings + WebSocket reconnect ceiling
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 32dd4e1 commit 7380638

4 files changed

Lines changed: 36 additions & 9 deletions

File tree

backend/src/__tests__/linear-service.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,5 +492,11 @@ describe("buildLinearSummaryMarkdown", () => {
492492
expect(md).toContain("PR: https://github.com/org/repo/pull/12");
493493
expect(md).toContain("webmux-state:feat/foo");
494494
expect(md).toContain("0.31.0");
495+
// Guards against the rename regression that shipped --resume-from-linear
496+
// (and earlier --from-linear) instructions to users after the flag was
497+
// consolidated into --linear.
498+
expect(md).toContain("webmux oneshot --linear");
499+
expect(md).not.toContain("--resume-from-linear");
500+
expect(md).not.toContain("--from-linear");
495501
});
496502
});

backend/src/services/linear-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ export async function fetchAssignedIssues(options?: { skipCache?: boolean }): Pr
550550
return result;
551551
}
552552

553-
// ── Issue + attachments query (for resume-from-linear / post-to-linear) ─────
553+
// ── Issue + attachments query (for --linear / `webmux linear post`) ─────────
554554

555555
const ISSUE_WITH_ATTACHMENTS_QUERY = `
556556
query IssueWithAttachments($id: String!) {
@@ -821,7 +821,7 @@ export function buildLinearSummaryMarkdown(input: LinearSummaryInput): string {
821821
lines.push(`- Transcript: see attachment \`${input.attachmentTitle}\``);
822822
if (input.webmuxVersion) lines.push(`- webmux: ${input.webmuxVersion}`);
823823
lines.push("");
824-
lines.push("_Resume on another machine with_ `webmux oneshot --resume-from-linear <issue-id>`.");
824+
lines.push("_Resume on another machine with_ `webmux oneshot --linear <issue-id>`.");
825825
return lines.join("\n");
826826
}
827827

bin/src/oneshot.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -346,21 +346,31 @@ function handleConversationEvent(
346346
}
347347
}
348348

349+
// Cap reconnects so a permanently-down server can't leave the CLI hanging
350+
// silently. We reset the counter on every successful `open` — only consecutive
351+
// failed attempts (no `open` in between) count toward the limit.
352+
const MAX_CONSECUTIVE_RECONNECTS = 10;
353+
349354
function streamConversation(
350355
branch: string,
351356
port: number,
352357
state: ConversationPrintState,
353358
stderr: (line: string) => void,
359+
onFatal: (reason: string) => void,
354360
): { close: () => void } {
355361
let closed = false;
356362
let socket: WebSocket | null = null;
357363
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
364+
let consecutiveFailures = 0;
358365

359366
const connect = (): void => {
360367
if (closed) return;
361368
const url = `ws://localhost:${port}${apiPaths.streamAgentsWorktreeConversation.replace(":name", encodeURIComponent(branch))}`;
362369
const ws = new WebSocket(url);
363370
socket = ws;
371+
ws.addEventListener("open", () => {
372+
consecutiveFailures = 0;
373+
});
364374
ws.addEventListener("message", (event) => {
365375
if (typeof event.data !== "string") return;
366376
try {
@@ -373,6 +383,12 @@ function streamConversation(
373383
ws.addEventListener("close", () => {
374384
socket = null;
375385
if (closed) return;
386+
consecutiveFailures += 1;
387+
if (consecutiveFailures >= MAX_CONSECUTIVE_RECONNECTS) {
388+
closed = true;
389+
onFatal(`webmux server unreachable after ${consecutiveFailures} reconnect attempts`);
390+
return;
391+
}
376392
reconnectTimer = setTimeout(connect, 2000);
377393
});
378394
ws.addEventListener("error", () => {
@@ -619,7 +635,7 @@ export async function runOneshot(parsed: ParsedOneshotCommand, port: number): Pr
619635
// to stay consistent with `webmux add --from-linear`. The server still
620636
// accepts a `fromLinear.issueId` payload — it just doesn't need to re-fetch
621637
// because we pass the resolved branch + conversationContext explicitly.
622-
if (postToLinearTarget?.kind === "team" && !fromLinearIssueId) {
638+
if (postToLinearTarget?.kind === "team") {
623639
const title = deriveOneshotIssueTitle(parsed.prompt);
624640
if (!title) {
625641
stderr(`[${timestamp()}] [error] --linear ${postToLinearTarget.teamKey} requires --prompt to derive an issue title`);
@@ -731,25 +747,30 @@ export async function runOneshot(parsed: ParsedOneshotCommand, port: number): Pr
731747
// Conversation history may not yet be available for non-codex agents — fall through to streaming.
732748
}
733749

734-
const stream = streamConversation(branch, port, conversationState, stderr);
735-
const historyPoller = pollConversationHistory(branch, port, conversationState);
736-
737750
let resolveExit!: (code: number) => void;
738751
const exitPromise = new Promise<number>((resolve) => {
739752
resolveExit = resolve;
740753
});
741754
let exiting = false;
755+
let stream: { close: () => void } | null = null;
756+
let historyPoller: { stop: () => void } | null = null;
742757
let poller: { stop: () => void } | null = null;
743758
const finalize = (code: number): void => {
744759
if (exiting) return;
745760
exiting = true;
746-
stream.close();
747-
historyPoller.stop();
761+
stream?.close();
762+
historyPoller?.stop();
748763
poller?.stop();
749764
flushStreamingLine(conversationState);
750765
resolveExit(code);
751766
};
752767

768+
stream = streamConversation(branch, port, conversationState, stderr, (reason) => {
769+
stderr(`[${timestamp()}] [fatal] ${reason}`);
770+
finalize(1);
771+
});
772+
historyPoller = pollConversationHistory(branch, port, conversationState);
773+
753774
const pollState: PollState = {
754775
seenPrUrls: new Set(),
755776
seenMergedUrls: new Set(),

frontend/src/lib/LinearPostDialog.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
/>
6363
{#if teamKeyTrimmed && teamKeyLooksLikeIssue}
6464
<p class="mt-1 text-[11px] text-danger">
65-
Looks like an issue id. Use the "From Linear issue" field on the create-worktree dialog to start from an existing issue.
65+
Looks like an issue id. Start the worktree from the Linear panel (bottom-left) to seed it with that issue, then run `webmux linear post` again from the worktree to post the conversation back to it.
6666
</p>
6767
{:else if teamKeyTrimmed && !teamKeyValid}
6868
<p class="mt-1 text-[11px] text-danger">Expected a team key like ENG (uppercase letters only).</p>

0 commit comments

Comments
 (0)