diff --git a/packages/lint/src/rules/media.test.ts b/packages/lint/src/rules/media.test.ts
index 0fdee1bed..8237efa0d 100644
--- a/packages/lint/src/rules/media.test.ts
+++ b/packages/lint/src/rules/media.test.ts
@@ -566,8 +566,12 @@ describe("audio_group_no_members", () => {
const BUS = ``;
- it("errors on a bus no clip belongs to", async () => {
- const res = await lintHyperframeHtml(doc(BUS));
+ it("errors on a bus no clip in the file belongs to", async () => {
+ const res = await lintHyperframeHtml(
+ doc(
+ `${BUS}`,
+ ),
+ );
const finding = res.findings.find((f) => f.code === "audio_group_no_members");
expect(finding?.severity).toBe("error");
expect(finding?.elementId).toBe("voiceover");
@@ -597,6 +601,20 @@ describe("audio_group_no_members", () => {
// A bus with no id cannot be joined at all, and `resolveAudioGroups` skips it
// when building its element map — a different mistake, not this rule's.
+ // The rule can only speak about a file it can see all of. `lintHyperframeHtml`
+ // takes ONE file, and the studio's own group creation writes the bus into the
+ // active composition while patching `data-audio-group` into each member's own
+ // file (timelineAudioGroupCreate) — so a file holding a bus and no members at
+ // all is the normal cross-file shape, not a mistake.
+ it("stays quiet in a file that declares no members at all", async () => {
+ const res = await lintHyperframeHtml(
+ doc(
+ `${BUS}
`,
+ ),
+ );
+ expect(res.findings.some((f) => f.code === "audio_group_no_members")).toBe(false);
+ });
+
it("stays quiet for a bus with no id", async () => {
const res = await lintHyperframeHtml(
doc(``),
diff --git a/packages/lint/src/rules/media.ts b/packages/lint/src/rules/media.ts
index cd1396dc7..cb0576ce9 100644
--- a/packages/lint/src/rules/media.ts
+++ b/packages/lint/src/rules/media.ts
@@ -800,6 +800,15 @@ function findAudioGroupNoMembersFindings(ctx: LintContext): HyperframeLintFindin
.filter((id): id is string => Boolean(id)),
);
+ // Only a file that declares SOME membership can be judged. `lintHyperframeHtml`
+ // sees one file, and the studio's own group creation writes the bus into the
+ // active composition while patching `data-audio-group` into each member's own
+ // file (`timelineAudioGroupCreate`) — so a file carrying a bus and no members
+ // at all is the ordinary cross-file shape. Firing there reported the studio's
+ // own output as an error, and said "No clip carries `data-audio-group` at all"
+ // about clips it simply could not see.
+ if (memberGroupIds.size === 0) return [];
+
const findings: HyperframeLintFinding[] = [];
for (const tag of ctx.tags) {
if (tag.name !== "hf-audio-group") continue;
@@ -814,8 +823,8 @@ function findAudioGroupNoMembersFindings(ctx: LintContext): HyperframeLintFindin
const nearby = [...memberGroupIds].filter((id) => id !== elementId);
const suffix =
nearby.length > 0
- ? ` Clips name ${nearby.map((id) => `"${id}"`).join(", ")} instead.`
- : " No clip carries `data-audio-group` at all.";
+ ? ` Clips in this file name ${nearby.map((id) => `"${id}"`).join(", ")} instead.`
+ : "";
findings.push({
code: "audio_group_no_members",
severity: "error",