Skip to content

fix(stt): sign a Transcribe stream with its opening credentials - #17924

Closed
amadsalmon wants to merge 1 commit into
kirodotdev:mainfrom
amadsalmon:fix/transcribe-stream-credentials
Closed

amadsalmon wants to merge 1 commit into
kirodotdev:mainfrom
amadsalmon:fix/transcribe-stream-credentials

Conversation

@amadsalmon

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: A live Transcribe dictation on a named AWS profile is not cut off because the profile's credentials refreshed mid-stream.

Amazon Transcribe ended several live dictations 40 to 138 seconds in with "The request signature we calculated does not match the signature you provided." The streaming SDK asks the credential resolver again for every audio event, and each event's signature chains from the previous one, starting at the opening request. _ProfileCredentialResolver asked the boto3 session every time, so a profile whose credentials refreshed during the stream signed the next event with a different key.

Why it matters

The stream dies part-way through and the rest of the dictation is lost. Profiles backed by credential_process or SSO refresh on their own schedule, so this can hit any session that happens to straddle a refresh.

Not a goal

What changed (motivation → approach → change)

A stream should be signed with one key from start to end, so _ProfileCredentialResolver (transcribe.py) now resolves the profile's credentials once and returns the same ones on every later call. One resolver is built per live stream and per voice memo, so a new dictation still picks up fresh credentials. A live stream is capped at five minutes, and botocore refreshes credentials that have less than ten minutes left before handing them out, so the pinned credentials normally outlive the stream. The stt-streaming spec states this for the named-profile path.

Backwards compatibility

Compatible: the resolver's interface and callers are unchanged; it returns the same credentials it would have returned on the first call.

Removes nothing: no field, setting or reader goes away. A stream whose credentials were already close to expiry at open fails at expiry, where before it failed at the first refresh with a signature error.

Tests

  • test_a_stream_keeps_the_credentials_it_opened_with: real botocore RefreshableCredentials inside the refresh window, where every read refreshes to new keys; the resolver keeps returning the opening keys and asks the session once.
  • test_none_credentials_raises now builds the resolver through its constructor with boto3 patched, instead of __new__ plus a hand-set attribute.

prove.py: PROVEN (the new test fails with the fix reverted). The two resolver tests passed 20/20 at -n0.

Manual verification

N/A — unit coverage sufficient: the test drives botocore's real refresh logic with an injected clock, which is the exact mechanism that changes the key.

Related Issues

Part of #17842

Pattern harvest

Not generalizable: specific to an SDK that re-resolves credentials per event while chaining signatures across events.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@amadsalmon
amadsalmon requested a review from a team as a code owner October 7, 2026 23:47
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention labels Oct 7, 2026
amazon_transcribe asks the credential resolver again for every audio
event, and each event's signature chains from the opening request's.
_ProfileCredentialResolver asked the boto3 session every time, so a
profile whose credentials refreshed mid-stream signed the next event
with a different key, and Transcribe refused it with "the request
signature we calculated does not match".

Resolve once per resolver. One is built per live stream and per voice
memo. A live stream is capped at five minutes, inside the ten minutes
botocore leaves on any credentials it hands out.

Part of kirodotdev#17842
@amadsalmon
amadsalmon force-pushed the fix/transcribe-stream-credentials branch from f6267fa to 76f0b16 Compare October 8, 2026 00:34
@amadsalmon

Copy link
Copy Markdown
Contributor Author

The workflow runs for the current head are waiting for maintainer approval (fork PR, all at action_required). Could a maintainer approve them? This push removes the (#17842) docstring reference that failed the Comment History Gate on the previous head.

@amadsalmon

amadsalmon commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Status for head 76f0b1647: every red check is an infrastructure failure, not a finding on the diff.

  • Sensitive Change Review, Fork GPT 6.1 Review, Fork Opus 5.5 Review, Fork Internal Content Scan, Fork Security Scope Review: each died on API rate limit exceeded for installation between 00:44 and 00:47 UTC. Every fork review lane in the repo failed in that window.
  • Dependency License Gate: Dependency review is not supported on this repository (dependency graph off). It is failing on every PR right now, same-repo ones included.

A fork author cannot rerun jobs. Once the rate limit has reset, could a maintainer run:

gh run rerun 37708507509 --failed --repo kirodotdev/KiroCrew
gh run rerun 37709518862 --failed --repo kirodotdev/KiroCrew
gh run rerun 37709517682 --failed --repo kirodotdev/KiroCrew
gh run rerun 37709517645 --failed --repo kirodotdev/KiroCrew
gh run rerun 37709517672 --failed --repo kirodotdev/KiroCrew

@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Opus 5.5 Review (fork) — ✅ no blocking findings

Reviewed 76f0b1647ad8f5035bee595d99469a76f5f21916 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 76f0b16

@buluoray

Copy link
Copy Markdown
Contributor

Taking this over in #19550 since it has been idle; you are credited as co-author on the commits. Thanks for the groundwork.

@amadsalmon

amadsalmon commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Taking this over in #19550 since it has been idle; you are credited as co-author on the commits. Thanks for the groundwork.

@buluoray hi Ray, this PR is not idle. It is simply pending maintainer action.
I would like to keep full credit for the fix through both the PR and the commit.

I opened it only five days ago, but the issue is that the CI checks are so flaky and unreliable that it makes it hard to keep up with them and get them to pass to green.

I would appreciate some more support from the maintainers of this package to make it easier to pass the CI checks, without that support being taking over my PRs completely.

buluoray added a commit that referenced this pull request Oct 13, 2026
…s credentials (#19550)

* fix(stt): end dictation when Transcribe drops the stream

Amazon Transcribe can end the result stream mid-dictation (a refused
event signature, 15 s without audio). The gateway only awaited the
result stream at teardown, so the socket stayed open and the dashboard
kept showing Listening while every later word was lost.

A watcher task now waits on the result stream. If it ends before the
client stops, it sends an stt_session_failed error frame and closes the
socket, the way the apple relay already does. The duration cap and the
watcher share one first-claimant record, so only one frame goes out and
stt_stream_end records the cause that came first.

The new tests bound their reads with default_timeout() from
kiro_crew.testing.wait, which replaced _AUDIT_WAIT_TIMEOUT_SECS on main.

Supersedes #17925.

Co-authored-by: Amad Salmon <amad.salmon@icloud.com>

* fix(stt): sign a Transcribe stream with its opening credentials

The streaming SDK asks the credential resolver again for every audio
event, and each event's signature chains from the opening request's.
_ProfileCredentialResolver asked the boto3 session every time, so a
profile whose credentials refreshed mid-stream signed the next event
with a different key and Transcribe refused it with "signature does not
match".

The resolver, built once per stream, now resolves once and returns the
same credentials on every later call. botocore refreshes credentials
with under ten minutes left before handing them out, so the pinned ones
outlive the five-minute stream cap.

Supersedes #17924.

Co-authored-by: Amad Salmon <amad.salmon@icloud.com>

---------

Co-authored-by: Amad Salmon <amad.salmon@icloud.com>
@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Oct 13, 2026
@iamwhatever

Copy link
Copy Markdown
Contributor

Thank you, @amadsalmon. This fix is now on main: #19550 (commit 97beb72) carries "fix(stt): sign a Transcribe stream with its opening credentials" with you as co-author, and its message says it supersedes #17924. Closing this one as already merged. If anything here did not make it in, please reopen.

@github-actions github-actions Bot removed the readiness: checking Automated validation is still running label Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) merge conflict Branch has merge conflicts with its base — author must resolve before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants