- netdata-apps-fds-group-false-positive: the apps_group_file_descriptors_utilization false 100% on forking/root app groups (tailscaled on MajorToot 2026-05-15), the not-a-privilege gotcha, fleet-wide silence fix in MajorAnsible. - obs-stale-script-paths: pending from prior session (not on remote). - SUMMARY.md: link both (re-applied onto upstream after concurrent rebase). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
129 lines
4.8 KiB
Markdown
129 lines
4.8 KiB
Markdown
---
|
|
title: "OBS Studio — \"Error opening file: (null)\" After Windows Profile Rename"
|
|
domain: troubleshooting
|
|
category: streaming
|
|
tags: [obs, streaming, windows, lua, profile-migration]
|
|
status: published
|
|
created: 2026-05-14
|
|
updated: 2026-05-14
|
|
---
|
|
|
|
# OBS Studio — "Error opening file: (null)" After Windows Profile Rename
|
|
|
|
## Symptom
|
|
|
|
Loading a scene collection in OBS Studio triggers a popup like:
|
|
|
|
```
|
|
[<ScriptName>.lua] Error opening file: (null)
|
|
```
|
|
|
|
The `(null)` is the giveaway: OBS resolved the registered script path to nothing — the file doesn't exist where the scene collection says it does. Most commonly this happens after a Windows profile was renamed or migrated and `C:\Users\<old>\...` paths were not updated.
|
|
|
|
## Why it happens
|
|
|
|
OBS stores per-scene-collection Lua/Python script registrations inside the scene collection JSON at:
|
|
|
|
```
|
|
%APPDATA%\obs-studio\basic\scenes\<Collection>.json
|
|
```
|
|
|
|
Each entry under `modules.scripts-tool[]` is an absolute Windows path. Renaming the Windows profile does not rewrite these — the JSON keeps pointing at the old `C:\Users\<old>\...` location, and OBS surfaces the resolution failure as a `(null)` popup on collection load.
|
|
|
|
## Diagnose
|
|
|
|
From WSL (or any shell with access to `%APPDATA%`):
|
|
|
|
```bash
|
|
OBS_DIR="/mnt/c/Users/<current-windows-user>/AppData/Roaming/obs-studio"
|
|
|
|
# 1. List scene collections
|
|
ls "$OBS_DIR/basic/scenes/"
|
|
|
|
# 2. Find collections referencing the missing script
|
|
grep -l -i "<script-name-substring>" "$OBS_DIR/basic/scenes/"*.json
|
|
|
|
# 3. Dump the scripts-tool paths from each suspect collection
|
|
python3 -c "
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
for s in d.get('modules', {}).get('scripts-tool', []):
|
|
print(s.get('path'))
|
|
" "$OBS_DIR/basic/scenes/<Collection>.json"
|
|
```
|
|
|
|
If a printed path contains `C:/Users/<old-username>/...` and the file doesn't exist on disk, you've found it.
|
|
|
|
## Fix
|
|
|
|
> [!warning] Close OBS first
|
|
> OBS rewrites the scene collection JSON when it exits. Any edit made while OBS is running will be overwritten. Confirm with `tasklist.exe | grep obs64` (WSL) or Task Manager.
|
|
|
|
### 1. Make the missing script reachable
|
|
|
|
Either:
|
|
|
|
- **Re-extract / restore the script** to a path under the new profile (recommended — gives you a clean canonical home), or
|
|
- **Leave it in the rescue/migration folder** and point OBS there (fragile if the rescue folder is later deleted).
|
|
|
|
### 2. Back up the scene collection JSON
|
|
|
|
```bash
|
|
SCENES="/mnt/c/Users/<current-windows-user>/AppData/Roaming/obs-studio/basic/scenes"
|
|
STAMP="$(date +%Y%m%d-%H%M%S)"
|
|
cp -p "$SCENES/<Collection>.json" "$SCENES/<Collection>.json.$STAMP.bak"
|
|
```
|
|
|
|
### 3. Rewrite the paths atomically
|
|
|
|
Edit the JSON in place by parsing it, replacing the matched path strings, and writing through a temp file (so a crash mid-write can't corrupt the collection):
|
|
|
|
```bash
|
|
python3 <<'PY'
|
|
import json, os
|
|
scenes = "/mnt/c/Users/<current-windows-user>/AppData/Roaming/obs-studio/basic/scenes"
|
|
mapping = {
|
|
"C:/Users/<old>/Pictures/.../<script>.lua":
|
|
"C:/Users/<new>/Pictures/.../<script>.lua",
|
|
}
|
|
for fn in ("<Collection>.json",):
|
|
path = os.path.join(scenes, fn)
|
|
d = json.load(open(path))
|
|
for entry in d.get("modules", {}).get("scripts-tool", []):
|
|
if entry.get("path") in mapping:
|
|
entry["path"] = mapping[entry["path"]]
|
|
tmp = path + ".tmp"
|
|
json.dump(d, open(tmp, "w"), indent=4)
|
|
os.replace(tmp, path)
|
|
PY
|
|
```
|
|
|
|
OBS scene JSONs use forward slashes in Windows paths — preserve that style.
|
|
|
|
### 4. Verify
|
|
|
|
Re-run the diagnostic Python snippet and confirm every printed path resolves to a real file (translate `C:/` → `/mnt/c/` from WSL).
|
|
|
|
### 5. Reopen OBS
|
|
|
|
Load the scene collection. The popup should be gone.
|
|
|
|
## Why not just remove the script?
|
|
|
|
If the script is part of a third-party overlay pack (Twitch Pimpage, OWN3D, etc.), removing the registration also removes the overlay's source presets — fixing the path keeps the imported scenes intact. If you don't actually use the overlay anymore, removing the `scripts-tool` entry is fine; OBS will silently drop the broken reference on next save.
|
|
|
|
## Generalization
|
|
|
|
This same pattern applies to any OBS asset path stored in a scene collection or profile:
|
|
|
|
- Browser source local files
|
|
- Image / media source files
|
|
- Lua / Python script paths
|
|
- VST plugin paths
|
|
|
|
All of them are absolute, all of them survive a Windows profile rename in stale form, and all of them can be batch-rewritten with the same JSON-edit pattern above. Search for the old username substring across `%APPDATA%\obs-studio\` to catch them all in one pass.
|
|
|
|
## Related
|
|
|
|
- [[../../MajorInfrastructure/Devices/MajorRig|MajorRig device note]] — Incident Log 2026-05-14 (TTT/MLS scene popups) and 2026-05-07 (`majli` profile retirement that left these references stranded)
|
|
- [[../04-streaming/obs/obs-studio-setup-encoding|OBS Studio Setup and Encoding Settings]]
|