Skip to content

Canonical <video> + <audio> same-src pattern (SKILL.md "Video and Audio") triggers StaticGuard invalid-contract #586

@sidorovanthon

Description

@sidorovanthon

Describe the bug

The <video muted> + <audio> two-element pattern documented in skills/hyperframes/SKILL.md §"Video and Audio" — with the same src on both elements — produces a runtime StaticGuard contract violation when followed verbatim.

The compiler unconditionally injects data-has-audio="true" on every <video> without an explicit attribute (packages/core/src/compiler/timingCompiler.ts:104-106), regardless of whether the video file actually contains an audio stream. Combined with muted, this trips the StaticGuard rule at packages/core/src/lint/rules/media.ts:274 ("declares data-has-audio="true" but also has muted").

The lint rule duplicate_audio_track added in #299 only checks <audio><audio> overlaps, so it does not catch this case where a <video> and an <audio> reference the same src on a muxed file.

This matters because the SKILL.md canonical example uses identical src="video.mp4" on both elements — an agent that follows the skill canon literally will scaffold a project that fails its own validate.

Link to reproduction

https://github.com/sidorovanthon/hyperframes-repro-double-audio-same-src

Steps to reproduce

  1. Clone the repro repo above.
  2. npm install
  3. npx hyperframes validate

Expected behavior

Clean validation — no contract warning. The canonical example of the canonical pattern should not be flagged invalid by the canonical lint.

Actual behavior

◆  Validating my-video in headless Chrome
[StaticGuard] Invalid HyperFrame contract: <video id="el-v"> declares data-has-audio="true" but also has muted. Studio preview will silence the video audio.
◇  No console errors

In a real composition that uses this scaffold (talking-head muxed final.mp4), the studio preview also produces audible distortion. The capture engine output is clean (consistent with #298's "capture bypasses both DOM pipelines and muxes audio from source files" note).

Verified workarounds

  • Adding explicit data-has-audio="false" to the <video> element silences StaticGuard. Compiler skips auto-injection when the attribute is already present (!hasAttr(result, "data-has-audio") guard), and audioMixer's strict equality on "true" excludes this <video> from the mix. The two-element pattern then works as intended.
  • Stripping the audio stream from the video file (ffmpeg -an) does not help — the compiler still injects data-has-audio="true" because it does not probe the file.

Suggested fixes (any one is sufficient)

  1. Doc fix (cheap): update skills/hyperframes/SKILL.md §"Video and Audio" to include data-has-audio="false" on the <video> when both elements share src. Also surface the attribute in the All-Clips table — it is currently documented only in packages/cli/src/docs/data-attributes.md, which is not part of the agent-facing skill canon.
  2. Lint fix: extend duplicate_audio_track (fix: double-audio scaffold, lint rules, docs guide, Gemini 3.1 #299) to also flag a <video src=X muted> + <audio src=X> pair overlapping in time — functionally a duplicate audio track from the same muxed source.
  3. Compiler fix: make the data-has-audio auto-injection conditional — skip on <video muted>, or probe the source for an audio stream before injecting.

Environment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions