Repository navigation
feat(analytics): download a session's recording under --experimental-auth - #1019
Merged
Merged
Conversation
Topherhindman
force-pushed
the
devx-793-cli-session-participants
branch
from
October 9, 2026 02:57
fed8275 to
4859532
Compare
Topherhindman
force-pushed
the
devx-799-cli-session-recording
branch
from
October 9, 2026 02:58
f3686f4 to
1a70d9d
Compare
Topherhindman
force-pushed
the
devx-793-cli-session-participants
branch
from
October 9, 2026 05:10
4859532 to
0800de1
Compare
Topherhindman
force-pushed
the
devx-799-cli-session-recording
branch
from
October 9, 2026 05:10
1a70d9d to
10b2cbf
Compare
Topherhindman
marked this pull request as ready for review
October 9, 2026 06:27
Topherhindman
force-pushed
the
devx-799-cli-session-recording
branch
from
October 9, 2026 20:26
10b2cbf to
837b051
Compare
Topherhindman
force-pushed
the
devx-793-cli-session-participants
branch
from
October 9, 2026 20:26
0800de1 to
1b3665b
Compare
Topherhindman
force-pushed
the
devx-799-cli-session-recording
branch
from
October 9, 2026 20:50
837b051 to
80ff1a3
Compare
JackNDwyer
approved these changes
Oct 10, 2026
Topherhindman
force-pushed
the
devx-799-cli-session-recording
branch
from
October 10, 2026 03:11
80ff1a3 to
7c8b3b1
Compare
Picks up GetSessionRecordingURL's response as the server now returns it: the URL's expires_at and the recording's recording_started_at replace created_at, and the operation and its file_type and expiry_seconds params carry the server's descriptions (NotFound for a missing recording, UNSPECIFIED is InvalidArgument, expiry clamped to 15 minutes). Nothing hand-written reads the changed types, so only oapi.gen.go changes. Generated from livekit/public-api-server@7f379db
GetSessionRecordingURL takes the recording by name, "audio" or "chat-history", and rejects any other before sending a request. It returns the signed URL with its expiry and the recording's start. DownloadRecording fetches that URL with no credentials, since the signature is the authorization and the user's session token must not reach the object store. It uses its own http.Client, not http.DefaultClient, so no cookie jar or transport set elsewhere in the process can add any, and a stalled object store times out (30s to answer, 10 minutes for the whole download) instead of hanging lk. Its errors keep the signature out: a *url.Error loses the URL's query string, and the URL's long query values are hidden where an object store's error page echoes them. Chat history is stored gzip-encoded, so a gzip body is decompressed whether or not the transport already did; a refused URL is an error naming the status, never a saved error page. APIError now keeps the error envelope's typed details, which the REST transcoder writes as JSON beside an "@type". ObservabilityDisabled reads the dashboard link off the FailedPrecondition the server returns when nothing was recorded because user data recording is off, and IsNotFound recognizes a missing recording.
…auth `lk analytics session recording SESSION_ID --type audio|chat-history` asks the Public API to sign a URL for the recording and downloads it straight from the project's data region; nothing passes through the API. Audio saves as SESSION_ID-audio.ogg and chat history, decompressed, as SESSION_ID-chat-history.json. A file already at the default name is never replaced: the command refuses before asking for a URL and says to pass -o to replace it. A file -o FILE names is replaced, since the user chose it. The download lands in a hidden temporary file that is renamed into place when complete, so a failed download leaves no partial file and keeps an existing one. --url-only prints the signed URL on stdout (its expiry on stderr), and --json prints the API's response or, after a download, the saved file, its size and when the recording started. A missing recording says the session wasn't recorded, has expired, or ended less than a minute ago, or that there is no such session, since the API answers both the same way. With user data recording off it says so and links the project's observability settings from the error's ObservabilityDisabled detail. A permission denial says reading the recording requires being a project admin: sessionAPIError now takes the access a read requires, and the participant list keeps saying the user doesn't have access to the project. Only the Public API signs recording URLs, so the command runs only under --experimental-auth, through sessionRead, with the signed-in user's session token, and refuses to run otherwise before reading its arguments.
A NotFound from the recording read said there might be no such session "in this project". A mistyped --project answers NotFound too, so the error now names the project it asked: "in project p_...".
Topherhindman
force-pushed
the
devx-799-cli-session-recording
branch
from
October 10, 2026 03:12
7c8b3b1 to
c42dea5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes DEVX-799
Depends on livekit/public-api-server#49.
lk analytics session recording SESSION_ID --type audio|chat-historyasks the Public API to sign a URL for a session's recording, then downloads it straight from the project's data region. Nothing passes through the API, and the user's session token never reaches the object store.What changed
chore(public)):oapi.gen.gois regenerated from livekit/public-api-server@7f379db, livekit/public-api-server#49's merge commit onmain. It picks upGetSessionRecordingURL's response as the server now returns it: the URL'sexpires_atand the recording'srecording_started_atreplacecreated_at. The operation's docs now say a missing recording is NotFound and the expiry is clamped to 15 minutes. Nothing hand-written reads the changed types, so onlyoapi.gen.gochanges.feat(public)):GetSessionRecordingURLtakes the recording by name,audioorchat-history, and rejects any other before sending a request. It returns the signed URL, its expiry and when the recording started.DownloadRecordingfetches the signed URL with no credentials, since the signature is the authorization. It uses its ownhttp.Client, so no cookie jar or transport set elsewhere in the process can add any. A stalled object store times out (30s to answer, 10 minutes for the whole download) instead of hanginglk.*url.Errorloses the URL's query string, and long query values are hidden where an object store's error page echoes them.APIErrorkeeps the error envelope's typed details.ObservabilityDisabledreads the dashboard link from the FailedPrecondition the server returns when user data recording is off.IsNotFoundrecognizes a missing recording.feat(analytics)):SESSION_ID-audio.ogg, and chat history, decompressed, asSESSION_ID-chat-history.json. A file already at the default name is never replaced: the command refuses before asking for a URL and says to pass-o. A file-o FILEnames is replaced, since the user chose it.--url-onlyprints the signed URL on stdout, so it pipes intocurl, and its expiry on stderr.--jsonprints the API's response, or after a download, the saved file, its size and when the recording started.sessionAPIErrornow takes the access a read requires, and the participant list keeps saying the user doesn't have access to the project.--experimental-auth, throughsessionRead.Usage
With
--json:{"sessionId":"RM_1","recording":"chat-history","file":"RM_1-chat-history.json","bytes":2048,"recordingStartedAt":"2026-10-07T11:00:00Z"}Since review
Testing
go build ./...,go vet ./pkg/... ./cmd/lk/andgo test ./pkg/... ./cmd/lk/pass on every commit.pkg/public:TestGetSessionRecordingURL,TestGetSessionRecordingURLRejectsUnknownRecording,TestGetSessionRecordingURLMissingURL,TestGetSessionRecordingURLErrors,TestDownloadRecording(gzip handling),TestDownloadRecordingRefused,TestDownloadRecordingSendsNoCredentialsandTestDownloadRecordingErrorsHideSignature.pkg/public/render:TestRecordingURLandTestRecordingSaved.cmd/lk:TestSessionRecordingCommand,TestSessionRecordingRequiresExperimentalAuth,TestSessionRecordingOptions,TestFetchSessionRecording,TestFetchSessionRecordingURLOnly,TestFetchSessionRecordingJSON,TestFetchSessionRecordingNothingToDownload,TestFetchSessionRecordingDownloadFails,TestFetchSessionRecordingExistingFileandTestDefaultRecordingFile.