Skip to content

fix(stt): bound provider lifecycles - #163

Merged
AltanS merged 3 commits into
AltanS:mainfrom
en-ver:fix/stt-provider-lifecycle
Sep 5, 2026
Merged

fix(stt): bound provider lifecycles#163
AltanS merged 3 commits into
AltanS:mainfrom
en-ver:fix/stt-provider-lifecycle

Conversation

@en-ver

@en-ver en-ver commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Bounds STT provider work to the request that owns it.

  • Lifecycle cancellation now uses native AbortSignal.timeout and caller-first AbortSignal.any, while preserving the winning source/reason needed to distinguish caller cancellation from a provider deadline.
  • Caller cancellation now releases admission promptly, while late provider outcomes remain observed.
  • OpenAI retains its existing 60s lifecycle across fetch and capped response-body reading, with no retry.
  • Codex keeps one existing 120s lifecycle across auth, the first attempt, one 401 refresh/retry, and response bodies; identity probing is likewise bounded by one 120s lifecycle across the probe sequence.
  • Stream and process cleanup are abort-aware and bounded, so cleanup cannot hold completion.

Failure modes fixed

An abandoned caller could previously leave a two-slot admission occupied until a noncooperative provider settled. Codex work could also extend beyond its intended lifecycle budget across auth, retries, response bodies, or identity probing, making failure handling unbounded. This change releases abandoned admissions and carries the owning lifecycle through those paths.

Compatibility

Wire and audit schemas remain unchanged; caller cancellation now completes promptly through the existing 400 stt.unreadable / audit invalid mapping with the known audio byte count, rather than waiting for a late provider outcome. The 8MiB request cap, OpenAI's no-retry policy and 256KiB response cap, Codex identity order, and Codex's single retry are unchanged. The numeric admission limit remains two, but it now bounds requests Collie is still waiting on rather than every provider operation still in flight. Owned fetch and response-body work aborts with the caller lifecycle; owned Codex brokers retain explicit disposal, while shared broker work is intentionally not canceled. Noncooperative late work remains rejection-observed, with nonblocking best-effort response-reader cleanup. There is no request-intake streaming or request-body/upload deadline, Bun.serve or server-timeout manipulation, or server, web, configuration, dependency, version, or changelog change.

Validation

  • STT: 136 tests; server: 184; CLI STT: 34.
  • Root and web typechecks; lint; 2700 bridge tests; and the collie-ctl suite.
  • Corrected full web suite: cd web && NODE_OPTIONS=--no-experimental-webstorage bun run test — 181 files passed, 5029 tests passed, 30 todo.
  • Version/tag tests, feature-range tag and diff checks, and the cumulative regression: 20/20 isolated runs.
  • The source/reason regression covers a caller-originated DOMException(..., "TimeoutError") mapping to SttCancelledError.

The native Darwin full root test reaches only the unchanged upstream Linux systemd-run assertion at scripts/collie-cli.test.sh:1308; GitHub Ubuntu CI is required for that assertion. Tests use injected fetches, brokers, and streams, with no external provider or network calls, Codex process, browser, or microphone.

@github-actions github-actions Bot added area: bridge Bun/TS bridge — Herdr socket, state engine, polling, REST bug Something isn't working labels Sep 4, 2026
@AltanS

AltanS commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Thanks, Enver. Your continued work on the STT path is much appreciated, and this one closes a real hole: an abandoned caller pinned an admission slot until the provider settled. The fix is sound and the tests are thorough.

Two asks before I merge:

  1. Reuse the repo's signal idiom. web/src/lib/api.ts already composes AbortSignal.any with AbortSignal.timeout, and Bun 1.4 supports both on the bridge. Most of the controller and listener bookkeeping in createSttDeadline collapses into that pair. Keep wait(), that is the part that stops waiting on shared Codex broker work without cancelling it. So: standard signal composition, plus a small wait() helper.

  2. Say what changed about the two-slot cap. The number is unchanged, but it now bounds observed work, not in-flight work. In practice that is fine, since the caller signal reaches fetch and kills the real connection. The PR body says "unchanged"; please state the new meaning instead.

One judgment call I leave to you: a cancelled request is audited as invalid / stt.unreadable. That matches what an abort during upload already did, so it is consistent. A distinct cancelled outcome would read more honestly in the audit log. Add it if you like; either is fine with me.

@en-ver

en-ver commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, @AltanS. C now uses native AbortSignal.any/AbortSignal.timeout plus a small observing wait(). The PR body clarifies the numeric-two observed-request cap, and validation passed.

@AltanS
AltanS merged commit 43c9cfe into AltanS:main Sep 5, 2026
3 checks passed
AltanS added a commit that referenced this pull request Sep 5, 2026
PRs #163, #165 and #168 came from forks, so they carried no CHANGELOG line of
their own, per the fork rule in CLAUDE.md. One line each, in landing order,
without hashes; the release commit adds those.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@en-ver
en-ver deleted the fix/stt-provider-lifecycle branch September 5, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: bridge Bun/TS bridge — Herdr socket, state engine, polling, REST bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants