Cosmetic follow-up to #537 (doc-only). - Virtuoso: README + CHANGELOG used lowercase `got-feedback/feedback-plugin-virtuoso`; the canonical repo (like every other got-feedback repo) is capital-B `feedBack-plugin-virtuoso`. Brought it in line with the sibling rows. - Community plugin references in CLAUDE.md, TODO.md, docs/, and the bundled tuner README were over-renamed to `feedBack-*` by the rename, but those repos are owned by community members who never renamed them (topkoa/stems+notedetect, OmikronApex/tuner, masc0t/update-manager). Restored their real `slopsmith-*` names. got-feedback's own `feedBack-*` references in the same files are left untouched. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
19 KiB
FeedBack Diagnostics Bundle — Format Specification
This document is the authoritative reference for the feedBack-diag-*.zip
file produced by Settings → Export Diagnostics (feedBack#166).
The bundle is consumed by humans (maintainers reading bug reports) and
AI agents (auto-triage, code-aware assistants). Every JSON file inside
the zip carries an explicit schema field so consumers can dispatch by
version without guessing.
Overview
A diagnostic bundle is a plain ZIP archive. The default filename is:
feedBack-diag-<feedBack-version>-<YYYYMMDD-HHMMSS>.zip
Top-level layout:
feedBack-diag-0.2.4-20260503-143022.zip
├── manifest.json AI-friendly index, schema 1
├── README.txt Human-friendly: what's in here, how to read
├── system/
│ ├── version.json feedBack + python + OS
│ ├── env.json allowlisted env vars only (no secrets)
│ ├── hardware.json backend hardware (container-limited if Docker)
│ └── plugins.json loaded + orphan plugins, with git info
├── logs/
│ ├── server.log tail of LOG_FILE (last ~5 MB), redacted if requested
│ ├── server.pretty.log human-readable companion when LOG_FORMAT=json (auto-detected)
│ └── server.log.meta.json
├── client/
│ ├── console.json all console levels + window errors + rejections
│ ├── hardware.json browser-visible hardware: WebGL/WebGPU, host OS
│ ├── local_storage.json filtered
│ └── ua.json browser, screen, page URL on export
└── plugins/<plugin_id>/ per-plugin contributed diagnostics
Sections are conditional on the user's include toggles (system, hardware,
logs, console, plugins). Missing sections are not represented in
manifest.json's files array.
manifest.json (bundle-level, schema 1)
{
"schema": 1, // bundle schema; bump = breaking change
"exported_at": "2026-05-03T14:30:22Z",
"feedBack_version": "0.2.4",
"runtime": "docker", // "docker" | "electron" | "bare"
"redacted": true, // were redactions applied?
"files": [
{ "path": "system/version.json", "kind": "json", "schema": "system.version.v1", "size": 312 },
{ "path": "logs/server.log", "kind": "text", "lines": 41203, "size": 5242880 }
],
"redactions": { // present when redacted=true
"paths_replaced": 142,
"ips_replaced": 3,
"song_names_replaced": 27,
"secrets_replaced": 1
},
"notes": [
"container masks host CPU/RAM in system/hardware.json — real host info lives in client/hardware.json"
]
}
Field semantics:
schema: 1— top-level bundle schema. Increment only on breaking changes to the layout (file moves, mandatory new sections). New optional fields are NOT a schema bump; consumers must ignore unknown keys.runtime— single source of truth for "where was this bundle produced" so an agent can pick the right interpretation rules. See Runtime kinds.files[].schema— present only when the file's first-level JSON object carries a stringschemafield (e.g."system.hardware.v1").files[].kind—"json"|"text"|"binary".notes— human-readable callouts. Always present; may be empty.
Per-file schemas
system.version.v1 — system/version.json
{
"schema": "system.version.v1",
"feedBack_version": "0.2.4",
"python": { "version": "3.12.4", "implementation": "CPython", "executable": "/usr/bin/python" },
"os": { "system": "Linux", "release": "6.5.0", "machine": "x86_64" },
"exported_at": "2026-05-03T14:30:22Z"
}
system.env.v1 — system/env.json
{
"schema": "system.env.v1",
"vars": {
"LOG_LEVEL": "INFO",
"LOG_FORMAT": "json",
"FEEDBACK_RUNTIME": "electron"
}
}
Allowlisted env var keys only (see ENV_ALLOWLIST in lib/diagnostics_bundle.py):
LOG_LEVEL, LOG_FORMAT, LOG_FILE, FEEDBACK_RUNTIME, PORT, HOST,
TZ, PYTHONUNBUFFERED, DEMUCS_SERVER_URL. New entries require an
allowlist edit; secrets must never be added.
system.hardware.v1 — system/hardware.json
{
"schema": "system.hardware.v1",
"runtime": { "kind": "docker", "in_docker": true, "in_kubernetes": false },
"os": { "system": "Linux", "release": "6.5.0", "version": "...", "machine": "x86_64" },
"cpu": {
"brand": "AMD Ryzen 9 7950X 16-Core Processor",
"arch": "x86_64",
"cores_logical": 32,
"cores_physical": 16,
"freq_mhz_current": 4500,
"freq_mhz_max": 5700
},
"memory": { "total_bytes": 67108864000, "available_bytes": 42000000000 },
"gpu": [
{
"source": "nvidia-smi",
"name": "NVIDIA GeForce RTX 4070",
"driver": "550.54.14",
"memory_total_mb": 12282
}
],
"notes": ["container masks host CPU/RAM"]
}
gpu is a list (zero, one, or many entries). Source values in the wild:
"nvidia-smi", "rocm-smi", "system_profiler". Container deployments
without NVIDIA Container Toolkit will have an empty list and a notes
entry explaining why.
system.plugins.v1 — system/plugins.json
{
"schema": "system.plugins.v1",
"plugins": [
{
"id": "stems",
"name": "Stems",
"version": "1.2.0",
"type": null,
"loaded": true,
"has_screen": true,
"has_script": true,
"has_settings": false,
"has_routes": true,
"diagnostics_declared": true,
"dir": "stems",
"standards": ["capability-pipelines.v1"],
"capabilities": {
"stems": { "roles": ["owner", "provider"], "commands": ["mute", "restore"] }
},
"capability_validation_warnings": [],
"capability_unsupported_versions": [],
"compatibility_shims": [],
"git": { "sha": "abc123d", "remote": "https://github.com/topkoa/slopsmith-plugin-stems.git" }
}
],
"orphans": [
{
"id": "broken",
"name": "Broken Plugin",
"version": "0.1.0",
"loaded": false,
"dir": "broken",
"path": "/home/user/.config/feedBack/plugins/broken"
}
]
}
orphans covers plugin directories that contain a plugin.json but are
NOT in LOADED_PLUGINS. Two sub-cases:
- Failed-to-load (no
evictedfield): the plugin id is not loaded at all — usually requirements install failure or manifest error. A plugin appearing only inorphanswithoutevictedis the single best diagnostic signal for "user installed plugin X but it's not working". - Evicted/superseded (
"evicted": true): the plugin id IS loaded, but from a different directory. Typical cause: bundled-wins logic discarded an old user-installed clone in favour of the in-tree copy. Also covers bundled plugin directories whose routes failed and whose server fell back to a user copy (the bundled dir then has a different path from the loaded entry). Check the server startup log for the specific failure reason.
dir is the bare directory name. path is the full resolved absolute path
to the orphan directory — the key disambiguator when the bundled copy and a
user-installed copy share the same directory name (e.g. both highway_3d).
In a redacted bundle path has home-dir and config-dir prefixes replaced
with placeholder tokens (e.g. <HOME>/..., <CONFIG_DIR>/...) so
filesystem paths and usernames do not leak.
Capability fields are redaction-safe manifest metadata. Invalid capability
declarations are excluded from capabilities and explained in
capability_validation_warnings; legacy surfaces still appear as
compatibility_shims so maintainers can see which old fields were bridged
into the capability model. Unsupported future capability-pipelines versions
appear in capability_unsupported_versions and should be treated as
non-executable runtime intent.
Client-side capability snapshots contributed under plugins/capabilities/client.json
use schema feedBack.capabilities.diagnostics.v1. They include current
pipelines, participants, conflicts, missing providers, user overrides, active
or orphaned claims, claim lifecycle records, compatibility shim hit counts,
unsupported-version reports, and recent decisions. The runtime caps this
snapshot at 64 KB by trimming older recentDecisions first while preserving
current graph state.
logs.server.v1 — logs/server.log.meta.json
{
"schema": "logs.server.v1",
"log_file": "/data/log/feedBack.log",
"exists": true,
"size_bytes": 8388608,
"tail_bytes": 5242880,
"truncated": true
}
The companion logs/server.log is the raw text tail (UTF-8). When
LOG_FORMAT=json, every line is independently parseable as JSON.
When the file exceeds 5 MB, the partial first line is dropped before
serialization so log parsers don't choke.
When the tail is JSON-per-line (auto-detected by content, not by env
var), an additional logs/server.pretty.log companion is written:
human-readable lines of the form <timestamp> [<LEVEL>] <event> k=v k=v. Mixed-format tails (a config flip mid-run) preserve non-JSON
lines verbatim. The original server.log is still emitted unchanged
for machine consumers. server.log.meta.json:pretty_companion is set
to true whenever server.pretty.log is present.
client.console.v1 — client/console.json
{
"schema": "client.console.v1",
"entries": [
{
"t": 1714752622123,
"kind": "console", // "console" | "error" | "rejection"
"level": "warn", // "log" | "info" | "warn" | "error" | "debug"
"msg": "WebSocket disconnected: 1006",
"args": ["WebSocket disconnected: 1006"],
"ua": "Mozilla/5.0 ...",
"screen": { "width": 2560, "height": 1440, "devicePixelRatio": 1, "colorDepth": 24 }
},
{
"t": 1714752623456,
"kind": "rejection",
"level": "error",
"msg": "fetch failed",
"stack": "Error: ...\n at ...",
"ua": "...",
"screen": { ... }
}
]
}
Bounded ring buffer: 500 entries, ~250 KB cap. Each entry's args may
contain truncated stringifications of non-string console arguments —
depth limit 4, key cap 30, string truncation at 1024 chars, circular refs
serialized as "[circular]".
client.hardware.v1 — client/hardware.json
{
"schema": "client.hardware.v1",
"runtime": {
"kind": "electron",
"electron": "28.1.0",
"chrome": "120.0.6099.109",
"node": "18.18.2",
"v8": "12.0.267.8",
"app_version": "0.2.4"
},
"navigator": {
"userAgent": "Mozilla/5.0 ...",
"platform": "Win32",
"hardwareConcurrency": 16,
"deviceMemory": 8,
"languages": ["en-US"]
},
"userAgentData": {
"platform": "Windows",
"platformVersion": "15.0.0",
"architecture": "x86",
"model": "",
"bitness": "64"
},
"screen": { "width": 2560, "height": 1440, "devicePixelRatio": 1, "colorDepth": 24 },
"webgl": {
"available": true,
"vendor": "Google Inc. (NVIDIA)",
"renderer": "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0)",
"version": "WebGL 2.0 (OpenGL ES 3.0 Chromium)",
"shading_language_version": "WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)",
"max_texture_size": 16384,
"redacted": false
},
"webgpu": {
"available": true,
"adapter_info": {
"vendor": "nvidia",
"architecture": "ada",
"device": "",
"description": ""
}
}
}
runtime.kind rules:
"electron"ifnavigator.userAgentcontainsElectron/. Versions populated when the desktop launcher exposeswindow.feedBackElectronvia a preloadcontextBridge."browser"otherwise.
webgl.redacted: true indicates the browser refused to expose the real
renderer string (Firefox privacy mode, Safari ≥17). Treat the vendor
and renderer fields as advisory in that case.
client.local_storage.v1 — client/local_storage.json
{
"schema": "client.local_storage.v1",
"data": { "<key>": "<value as string>" }
}
Every key/value in browser localStorage at export time. Plugins
typically prefix their keys with their plugin_id.
client.ua.v1 — client/ua.json
{
"schema": "client.ua.v1",
"userAgent": "...",
"url": "https://feedBack.local/",
"screen": { ... }
}
Plugin diagnostics — plugins/<plugin_id>/...
Per-plugin directory. Two ways to populate it:
diagnostics.server_files— relpaths underconfig_dir, copied verbatim. Same allowlist semantics assettings.server_files.diagnostics.callable—<module>:<function>; called with({"plugin_id", "config_dir"}). Return values:dict/list→ written toplugins/<id>/callable.jsonbytes→ written toplugins/<id>/callable.binstr→ written toplugins/<id>/callable.txt- other types → discarded with a warning
Exceptions are caught and logged to the bundle's
manifest.notes— a buggy plugin never crashes the export.
Plugins are encouraged to embed their own schema field
("<plugin_id>.diag.v1") in any JSON they emit so future tooling can
dispatch by plugin schema.
Runtime kinds
manifest.runtime and system/hardware.json:runtime.kind and
client/hardware.json:runtime.kind may take these values:
| Kind | Backend sees… | Frontend sees… | Cross-correlate? |
|---|---|---|---|
docker |
container-limited | host | NO — different machines |
electron |
host (Python is child) | host | YES — same machine |
bare |
host | host | YES — same machine |
Detection precedence (backend):
FEEDBACK_RUNTIMEenv var ("electron"/"docker"/"bare")/.dockerenvexists OR/proc/1/cgroupmentionsdocker/containerd/kubepods→docker- Parent process name matches
electronorFeedBack→electron - Default:
bare
Detection (frontend): Electron/ in user agent → electron, else
browser.
Redaction
Applied to logs/server.log text and client/console.json entry
messages when redact: true (default). The bundle's
manifest.json:redactions reports per-token-class counts.
Token grammar (stable within a single bundle, salted differently between bundles):
| Token | Source |
|---|---|
<DLC_DIR> |
configured DLC root path |
<HOME> |
user's home directory |
<CONFIG_DIR> |
feedBack config directory |
<song:HASH8> |
song filename / basename (8-char salted SHA-256) |
<ip:HASH6> |
IPv4 / IPv6 address |
<redacted> |
bearer token, key=/token=/api_key= query strings |
hardware.json and plugins.json are NOT redacted (no PII).
local_storage.json always has values for keys matching secret-name
patterns (api_key, token, secret, password, auth, bearer,
etc.) replaced with "<redacted>" — this happens unconditionally,
regardless of the main redaction toggle, because plugin authors
commonly store tokens in localStorage.
Versioning policy
- Bundle schema (
manifest.schema): integer. Bump on breaking layout changes (file relocations, removed required sections, incompatible structural changes to existing schemas). Today:1. - Per-file schemas (
<area>.<name>.v<n>): bumped independently. A bundle MAY mix old and new file schemas during transitions. - Adding optional fields to an existing schema is NOT a bump. Consumers MUST ignore unknown keys.
- Removing a field is a bump.
Bundles older than the consumer's known schemas should be processed on a best-effort basis (display what's recognized, warn about the rest).
AI agent reading guide
Start at manifest.json. It lists every file with its schema id —
dispatch on schema, never on path or filename heuristics.
Common symptom → file map:
| Symptom | Files to inspect |
|---|---|
| Audio not playing | system/plugins.json (stems plugin loaded?), grep logs/server.log for ffmpeg/vgmstream, client/console.json for fetch errors |
| 3D highway slow / black | client/hardware.json (webgl.renderer, webgpu.adapter_info); client/console.json for WebGL warnings |
| Plugin error on load | grep logs/server.log for Plugin %r, check system/plugins.json:orphans for failed-to-load |
| WebSocket disconnects | client/console.json (level: "warn" / "error") |
| "Works on my machine" | Diff system/version.json + system/env.json + system/hardware.json between bundles |
| Song-specific bug | grep logs/server.log for the song's <song:HASH> token (stable across the bundle) |
| Cross-platform crash | manifest.runtime + system/hardware.json:runtime + client/hardware.json:runtime |
| Cache / disk issue | system/env.json:LOG_FILE, logs/server.log.meta.json:exists |
When the bundle was redacted, the redaction token map is documented
above. Two log lines mentioning <song:a3f1c2> are about the same song
— but a bundle exported separately with the same song will use a
different token.
When manifest.runtime == "docker", the backend system/hardware.json
reports container-limited values. Real host CPU / RAM / GPU live in
client/hardware.json only. Don't cross-correlate.
When manifest.runtime == "electron", both halves describe the same
machine.
Plugin contribution contract
// plugin.json
{
"id": "nam_tone",
"name": "NAM Tone",
"version": "1.0.0",
"diagnostics": {
"server_files": ["nam_tone.db.diag.json"],
"callable": "diagnostics:collect"
}
}
Frontend plugins push diagnostics by calling
window.feedBack.diagnostics.contribute(plugin_id, payload) before the
user clicks Export. The payload is written to plugins/<id>/client.json
(gated on the same "Plugin diagnostics" toggle as backend plugin files).
Backend callable signature:
# plugins/nam_tone/diagnostics.py
def collect(ctx: dict) -> dict | bytes | str:
"""ctx: {'plugin_id': 'nam_tone', 'config_dir': Path(...)}"""
return {
"schema": "nam_tone.diag.v1",
"models": [...],
}
Best practices:
- Return small payloads (< 100 KB). Diagnostics are not a backup channel.
- Embed your own
schemafield in returned dicts. - Never raise — but if you do, the export keeps going and notes the failure.
- Don't include user secrets, API keys, or session tokens.