Skip to content

Commit aee5b9d

Browse files
dylanjeffersclaude
andauthored
fix: don't sign media links with an empty cid (#993)
## Problem BURKO stems downloads were failing (incident 2026-07-16, ~20:22 UTC). The parent track `qJ0RAXE` (1960897973) has `track_cid = null` and `orig_file_cid = null` — an upload-v2 row whose cid backfill never ran (`audio_upload_id` is set and the mediorum upload record is complete). The Tracks hydrator signed whatever cid it was given, so `/v1/tracks/{id}/download` and `/stream` 302'd to `tracks/cidstream/` with an **empty cid** in the signed payload, which every validator node 404s. That fed the archiver a guaranteed-404 URL and also silently broke playback for such tracks. ## Fix - `dbv1/tracks.go`: skip building the `stream`/`download` MediaLink when there is no cid to sign — the links are now `null` in track responses instead of pointing at a 404. - `v1_track_stream.go`: nil-guard before `redirectToStream` (which would nil-deref in `tryFindWorkingUrl`); returns 404 "track audio is unavailable". `/download` already 404s on a nil link. - Tests: fixture tracks 100/200 get a `track_cid` (previously every fixture signed an empty cid — `TestGetTrackDownload` was asserting the buggy URL); new `TestGetTrackDownload_NoCid` and `TestGetTrackStream_NoCid` cover the empty-cid → 404 behavior. Full `go test ./api/...` passes. ## Related - pedalboard#75 (already deployed) makes the archiver treat the parent track as best-effort, so stems archives survive a broken parent. - Follow-up needed: backfill `track_cid`/`orig_file_cid` for track 1960897973 from its mediorum upload record (`1cc9774d05f78bf91272c59080246ef8`) — until then the track can't stream or download at all (this PR just makes the failure honest). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 8b7d6bd commit aee5b9d

5 files changed

Lines changed: 77 additions & 7 deletions

File tree

api/dbv1/tracks.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,13 @@ func (q *Queries) TracksKeyed(ctx context.Context, arg TracksParams) (map[int32]
196196
}
197197
}
198198

199+
// A track row can have empty cid columns (e.g. an upload-v2 row whose
200+
// track_cid/orig_file_cid backfill never ran). Signing an empty cid
201+
// produces a content-node URL that is guaranteed to 404, so leave the
202+
// media link nil instead and let the endpoints report the track as
203+
// unavailable.
199204
var stream *MediaLink
200-
if access.Stream {
205+
if access.Stream && rawTrack.TrackCid.String != "" {
201206
stream, err = mediaLink(rawTrack.TrackCid.String, rawTrack.TrackID, arg.MyID.(int32), id3Tags)
202207
if err != nil {
203208
return nil, err
@@ -210,9 +215,11 @@ func (q *Queries) TracksKeyed(ctx context.Context, arg TracksParams) (map[int32]
210215
if cid == "" {
211216
cid = rawTrack.TrackCid.String
212217
}
213-
download, err = mediaLink(cid, rawTrack.TrackID, arg.MyID.(int32), nil)
214-
if err != nil {
215-
return nil, err
218+
if cid != "" {
219+
download, err = mediaLink(cid, rawTrack.TrackID, arg.MyID.(int32), nil)
220+
if err != nil {
221+
return nil, err
222+
}
216223
}
217224
}
218225

api/testdata/track_fixtures.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package testdata
22

33
var TrackFixtures = []map[string]any{
4-
{"track_id": 100, "genre": "Electronic", "owner_id": 1, "title": "T1", "is_unlisted": "f", "is_downloadable": "t"},
4+
{"track_id": 100, "genre": "Electronic", "owner_id": 1, "title": "T1", "is_unlisted": "f", "is_downloadable": "t", "track_cid": "QmT1TrackCid"},
55
{"track_id": 101, "genre": "Alternative", "owner_id": 1, "title": "T2", "is_unlisted": "f", "is_downloadable": "f"},
66
{"track_id": 102, "genre": "Alternative", "owner_id": 2, "title": "T3", "is_unlisted": "f", "is_downloadable": "f"},
7-
{"track_id": 200, "genre": "Electronic", "owner_id": 2, "title": "Culca Canyon", "is_unlisted": "f", "is_downloadable": "f"},
7+
{"track_id": 200, "genre": "Electronic", "owner_id": 2, "title": "Culca Canyon", "is_unlisted": "f", "is_downloadable": "f", "track_cid": "QmCulcaCanyonCid"},
88
{"track_id": 201, "genre": "Alternative", "owner_id": 2, "title": "Turkey Time DEMO", "is_unlisted": "t", "is_downloadable": "f"},
99
{"track_id": 202, "genre": "Alternative", "owner_id": 2, "title": "Turkey Time (live)", "is_unlisted": "f", "is_downloadable": "f"},
1010
{"track_id": 300, "genre": "Electronic", "owner_id": 3, "title": "Follow Gated Download", "is_unlisted": "f",

api/v1_track_download_test.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,39 @@ func TestGetTrackDownload(t *testing.T) {
1414
req := httptest.NewRequest("GET", "/v1/tracks/eYZmn/download", nil)
1515
res, err := app.Test(req, -1)
1616
assert.NoError(t, err)
17-
assert.Contains(t, res.Header.Get("Location"), "signature=%7B%22data%22%3A%22%7B%5C%22cid%5C%22%3A%5C%22%5C%22%2C%5C%22timestamp%5C%22%3")
17+
assert.Contains(t, res.Header.Get("Location"), "tracks/cidstream/QmT1TrackCid")
18+
assert.Contains(t, res.Header.Get("Location"), "signature=%7B%22data%22%3A%22%7B%5C%22cid%5C%22%3A%5C%22QmT1TrackCid%5C%22%2C%5C%22timestamp%5C%22%3")
1819
assert.Contains(t, res.Header.Get("Location"), "filename=T1.mp3")
1920
}
2021

22+
// A downloadable track whose row has no orig_file_cid or track_cid (e.g. an
23+
// upload-v2 row that never got its cid backfill) must 404 rather than
24+
// redirect to a signed URL with an empty cid, which content nodes reject.
25+
func TestGetTrackDownload_NoCid(t *testing.T) {
26+
app := emptyTestApp(t)
27+
fixtures := database.FixtureMap{
28+
"tracks": []map[string]any{
29+
{
30+
"track_id": 1,
31+
"owner_id": 1,
32+
"title": "No Cid",
33+
"is_downloadable": true,
34+
},
35+
},
36+
"users": []map[string]any{
37+
{
38+
"user_id": 1,
39+
"handle": "testuser1",
40+
},
41+
},
42+
}
43+
database.Seed(app.pool.Replicas[0], fixtures)
44+
req := httptest.NewRequest("GET", "/v1/tracks/"+trashid.MustEncodeHashID(1)+"/download", nil)
45+
res, err := app.Test(req, -1)
46+
assert.NoError(t, err)
47+
assert.Equal(t, 404, res.StatusCode)
48+
}
49+
2150
func TestGetTrackDownload_Original(t *testing.T) {
2251
app := emptyTestApp(t)
2352
fixtures := database.FixtureMap{

api/v1_track_stream.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ func (app *ApiServer) v1TrackStream(c *fiber.Ctx) error {
3030
track := tracks[0]
3131

3232
if track.Access.Stream {
33+
// Stream is nil when the track row has no cid to sign (e.g. an
34+
// upload-v2 row that never got its track_cid backfilled).
35+
if track.Stream == nil {
36+
return fiber.NewError(fiber.StatusNotFound, "track audio is unavailable")
37+
}
3338
return app.redirectToStream(c, track.Stream)
3439
}
3540

api/v1_track_stream_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"net/http/httptest"
55
"testing"
66

7+
"api.audius.co/database"
8+
"api.audius.co/trashid"
79
"github.com/stretchr/testify/assert"
810
)
911

@@ -21,6 +23,33 @@ func TestGetTrackStream(t *testing.T) {
2123
assert.NotContains(t, location, "id3_title=")
2224
}
2325

26+
// A streamable track whose row has no track_cid (e.g. an upload-v2 row that
27+
// never got its cid backfill) must 404 rather than redirect to a signed URL
28+
// with an empty cid, which content nodes reject.
29+
func TestGetTrackStream_NoCid(t *testing.T) {
30+
app := emptyTestApp(t)
31+
fixtures := database.FixtureMap{
32+
"tracks": []map[string]any{
33+
{
34+
"track_id": 1,
35+
"owner_id": 1,
36+
"title": "No Cid",
37+
},
38+
},
39+
"users": []map[string]any{
40+
{
41+
"user_id": 1,
42+
"handle": "testuser1",
43+
},
44+
},
45+
}
46+
database.Seed(app.pool.Replicas[0], fixtures)
47+
req := httptest.NewRequest("GET", "/v1/tracks/"+trashid.MustEncodeHashID(1)+"/stream", nil)
48+
res, err := app.Test(req, -1)
49+
assert.NoError(t, err)
50+
assert.Equal(t, 404, res.StatusCode)
51+
}
52+
2453
func TestGetTrackStreamWithID3(t *testing.T) {
2554
app := testAppWithFixtures(t)
2655
req := httptest.NewRequest("GET", "/v1/tracks/eYJyn/stream?id3=true", nil)

0 commit comments

Comments
 (0)