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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions