feat(dgw): keep session recording logs out of the playable file list - #2001
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 3 commits into
Conversation
A session can now push its event log (`.slog`) while its video or terminal recording is being pushed. Each push writes its own file and manifest entry. A second push of the same kind is still rejected. The media stream keeps driving the disconnect window, the terminated state and the recording policy, so a log push never ends a recording or kills a session. Sessions that only push a log behave as before. Shadow streaming uses the last media file, and the recording player only plays WebM files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Let maintainers know that an action is required on their side
|
…logs list Released players and streamers treat every entry of `recording.json` `files` as media. A `.slog` pushed while the recording has media now goes to a new `logs` list instead, so `files` of a media recording only holds media. A log-only recording keeps its `.slog` in `files`, and `logs` is left out of the JSON when empty, so existing manifest shapes are unchanged. File names stay unique across both lists. The session ZIP download also packs the files listed in `logs`. Shadow streaming and the recording player no longer need to filter by file type, so those changes are reverted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A log pushed after the media push ended (for example a `.slog` generated after the session) creates a new in-memory recording entry. That entry started with `has_media = false`, so the log push drove the recording state: it was reported as connected, `/shadow` could stream the finished media file, and the entry was never removed after the log push disconnected. The entry now reads `has_media` from the manifest, and starts as last seen with no recording policy. Only a push that drives the recording marks it connected and applies the TTL and policy. Also covers the manifest shapes (media only, log only, media with a sidecar log, and a manifest written by Gateway 2026.3.0) with exact JSON round-trip tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Log disconnection can restart the cleanup TTL after the media deadline has already expired.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Separates session logs from playable media while preserving log-only manifest compatibility.
Changes:
- Adds media/log recording categories and concurrent push handling.
- Adds optional manifest
logsentries and ZIP inclusion. - Adds lifecycle and compatibility tests.
| File | Description |
|---|---|
devolutions-gateway/src/token.rs |
Classifies recording file types. |
devolutions-gateway/src/session.rs |
Adds a test session-manager mock. |
devolutions-gateway/src/recording.rs |
Manages separate media/log manifests and lifecycles. |
devolutions-gateway/src/api/jrec.rs |
Includes logs in recording ZIPs. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // The log push can outlive the media stream, and its manifest entry is completed on disconnect. | ||
| if ongoing.has_connected_push() { | ||
| debug!(%id, "Media stream expired while a log push is still connected"); | ||
| return; |

Players and streamers treat every entry in
recording.jsonfilesas something they can play. Released RDM even picks the log over the video when both are there. So once a recording with a video gets a.slog(live next to the video, or pushed afterwards, e.g. an AI-generated log from DVLS), older clients break.This adds an additive
logslist to the manifest:.slogpushed to a recording that has media goes tologs.fileskeeps only the media, so released players,/shadowand ZIP consumers behave the same..sloginfiles, exactly like today.logsis omitted when empty, so existing manifest shapes are byte-for-byte unchanged (pinned bymanifest_shapes_round_trip_unchanged)./shadowstream the old video.logs.Where to look hardest:
handle_connect/handle_removeinrecording.rs(who drives state, and cleanup when only the log push is left).Producers: open the log push after the media push is up, or use a separate push token. A log push that connects before any media lands in
files.Tests:
cargo test -p devolutions-gateway --lib(recording, jrec, token, streaming, session) 33/33,--test dvls_compatibility24/24, clippy clean.Stacked: #2002 caps the
.slogpush size.🤖 Generated with Claude Code