Skip to content

fix(web): flow cancel's non-interactive refusal serves a page - #346

Merged
cvince merged 1 commit into
feat/portabilityfrom
claude/flow-cancel-web-url
Aug 29, 2026
Merged

fix(web): flow cancel's non-interactive refusal serves a page#346
cvince merged 1 commit into
feat/portabilityfrom
claude/flow-cancel-web-url

Conversation

@cvince

@cvince cvince commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

capy flow cancel <id> --web exited 3 with no URL. The refusal itself was correct — loud, names the remedy — but under --web it reached nobody.

displayErrorAndExit is the wrong tool: it hardcodes exit 1, and this refusal's contract is EXIT_NEEDS_INPUT (3), the code an agent branches on to learn a question needs answering. So the page is served directly and the exit code is preserved. The sentences are refuseNonInteractive's own, carried whole.

Verified by running:

$ capy flow cancel <id> --web
  exit code: 3
  url line : http://127.0.0.1:50613/s/8dEYIfWQLnt8_w9Rlo05UliRoM1baGKz_FhhV4LBaXY

Why here and not in refuseNonInteractive

The central fix would cover all 13 call sites at once. But it means making that function async — and it is typed never, which every caller relies on for control flow. An unawaited async call would let execution continue past a guard meant to stop it, silently. That is a real refactor with a real failure mode, not a mechanical change, and it deserves to be done deliberately. Written up in the handoff rather than half-built.

Suite: 1851 pass, 0 fail.

`capy flow cancel <id> --web` exited 3 with no URL. The refusal itself was
correct — loud, and it names the remedy — but under --web it reached nobody.

displayErrorAndExit is the wrong tool here: it hardcodes exit 1, and this
refusal's contract is EXIT_NEEDS_INPUT (3), the code an agent branches on to
learn that a question needs answering. So the page is served directly and the
exit code is preserved. The sentences are refuseNonInteractive's own, carried
whole, so terminal and browser say the same thing.

Fixed at this call site rather than inside refuseNonInteractive, deliberately.
The central fix would cover all 13 call sites at once, but it means making that
function async — and it is typed `never`, which is what every caller relies on
for control flow. An unawaited async call would let execution continue past a
guard that is supposed to stop it, silently. That is a real refactor with a
real failure mode, not a mechanical change, and it should be done deliberately
rather than at the end of a long session.

Verified by running:

    $ capy flow cancel <id> --web
      exit code: 3
      url line : http://127.0.0.1:50613/s/8dEYIfWQLnt8_w9Rlo05UliRoM1baGKz_FhhV4LBaXY

Suite: 1851 pass, 0 fail.
@cvince
cvince merged commit cf43f2c into feat/portability Aug 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant