Reproduce a bug headlessly
verifiedAgainst 119c2c3 · verifiedOn 2026-09-10 · the automated staleness banner is planned, not built.
Opening the full Unity Editor GUI to reproduce a bug, verify an asset pipeline step, or inspect an object state requires interactive window focus and cannot be scripted in CI. Most diagnostic and verification work in Voxamine is performed headlessly via Unity -batchmode -nographics -executeMethod.
Unlike worked examples W1–W5, which build permanent game features, this page teaches a core developer technique: authoring a throwaway diagnostic entry point, running it through the headless command line, and parsing logs back to repository file:line locations in seconds.
What you will build & run
Section titled “What you will build & run”A headless diagnostic harness (HeadlessMeshAudit.cs) that:
- Opens the gameplay scene in batch mode without graphical overhead.
- Evaluates an in-memory system (e.g. verifying chunk meshing boundaries or terrain palette integrity).
- Emits structured diagnostic tokens (
[diag] ...). - Exits with a deterministic process code (
0for success,1for error) directly to the shell’s$?. - Logs all output to an isolated, fresh log file for immediate grep triage.
Where this sits
Section titled “Where this sits”Headless execution bypasses the entire rendering pipeline and window manager:
flowchart TD CLI["CLI Shell\nunity -batchmode -nographics -executeMethod"] BOOT["Unity Engine Bootstrap\nloads ProjectVersion.txt & assemblies"] LOCK["Lockfile Guard\nTemp/UnityLockfile check"] METHOD["Static Entry Point\nNamespace.Class.Method()"] LOG["Isolated Log File\nLogs/diagnostic.log"] EXIT["Process Exit Code\nEditorApplication.Exit(code) -> $?"] CLI --> LOCK LOCK --> BOOT BOOT --> METHOD METHOD --> LOG METHOD --> EXIT classDef cli fill:#1e1e24,stroke:#e58a63,stroke-width:1px,color:#f8f9fa; classDef engine fill:#0c4f48,stroke:#6fd8c6,stroke-width:1px,color:#f8f9fa; class CLI,EXIT cli; class BOOT,LOCK,METHOD,LOG engine;
Invariants
Section titled “Invariants”- Explicit exit codes: The static method must call
EditorApplication.Exit(0)on success andEditorApplication.Exit(1)on exception. Never rely on-quitalone;-quitmasks internal failure states and can race with method completion. - Quarantine to Editor assemblies: Diagnostic scripts live in
Assets/_Game/Editor/and are never packaged into standalone player builds. - Single-lockfile rule: Only one Unity process (GUI or batch) can hold
Temp/UnityLockfileat any time.
Before you start
Section titled “Before you start”Reading manifest
Section titled “Reading manifest”Read these files at pinned commit 119c2c3 before authoring:
| # | File | Symbol / What to extract |
|---|---|---|
| 1 | Assets/_Game/Editor/VoxelWorkshop/Modules/ProjectDoctor/StandaloneReleaseBuilder.cs |
Command-line -executeMethod signature, argument extraction, and EditorApplication.Exit(). |
| 2 | Assets/_Game/Editor/Gates/C1PGate.cs |
Headless verification gate execution pattern: [MenuItem] duality with batchmode runner. |
| 3 | Docs/BUILD_AND_TEST.md |
Canonical command-line arguments and batchmode flags across Linux/macOS/Windows. |
Intake decisions
Section titled “Intake decisions”| Decision | Choice | Why |
|---|---|---|
| Graphics flag | -nographics |
Use it for diagnostics that do not render. Omit it for HDRP capture, which needs a real graphics device. |
| Crash flag | -silent-crashes |
Prevents Linux OS modal crash dialogues from hanging unattended terminal processes. |
| Log destination | Dedicated Logs/<task>.log |
Prevents polluting the default Editor.log and guarantees a clean, single-session file. |
The technique, step by step
Section titled “The technique, step by step”Step 1: Confirm the project lock is free
Section titled “Step 1: Confirm the project lock is free”Before launching a batch process, verify that no other Unity process is accessing the project:
# Check if a Unity process is currently running in this repositorypgrep -fl "Unity.*Minecraft-HD"
# Check for a stale lockfilels -l Temp/UnityLockfile 2>/dev/null[!WARNING] If the Unity GUI is currently open on this project, batchmode will silently queue or block indefinitely until the GUI is closed. Close the GUI before launching headless diagnostic jobs.
Step 2: Write the diagnostic static method
Section titled “Step 2: Write the diagnostic static method”Create a temporary or permanent diagnostic class in Assets/_Game/Editor/Diagnostics/HeadlessMeshAudit.cs:
using System;using UnityEditor;using UnityEditor.SceneManagement;using UnityEngine;using VoxelSandbox.Data.Blocks;
namespace VoxelSandbox.Editor.Diagnostics{ public static class HeadlessMeshAudit { private const string Tag = "[diag-mesh]";
/// <summary> /// Entry point: -executeMethod VoxelSandbox.Editor.Diagnostics.HeadlessMeshAudit.Run /// </summary> public static void Run() { try { Debug.Log($"{Tag} Starting headless mesh audit...");
// Optional: open a specific test or gameplay scene // EditorSceneManager.OpenScene("Assets/_Game/Scenes/Gameplay.unity", OpenSceneMode.Single);
// Run your diagnostic assertions int auditCount = 0; var catalog = AssetDatabase.LoadAssetAtPath<BlockCatalog>("Assets/_Game/Data/Blocks/BlockCatalog.asset"); if (catalog == null) { throw new InvalidOperationException("BlockCatalog asset could not be loaded."); }
Debug.Log($"{Tag} Loaded catalog with {catalog.Definitions.Count} blocks.");
// Check specific condition under investigation for (int i = 0; i < catalog.Definitions.Count; i++) { var def = catalog.Definitions[i]; if (def != null) { auditCount++; } }
Debug.Log($"{Tag} PASS: Audited {auditCount} block definitions cleanly."); EditorApplication.Exit(0); } catch (Exception exception) { Debug.LogError($"{Tag} FAIL: Diagnostic threw exception: {exception}"); EditorApplication.Exit(1); } } }}Step 3: Extract custom command-line arguments (Optional)
Section titled “Step 3: Extract custom command-line arguments (Optional)”If your diagnostic requires dynamic inputs (such as a world seed, chunk coordinate, or target file path), extract arguments directly from the raw process string:
private static string GetCustomArg(string flagName){ string[] args = Environment.GetCommandLineArgs(); for (int i = 0; i < args.Length - 1; i++) { if (string.Equals(args[i], flagName, StringComparison.OrdinalIgnoreCase)) { return args[i + 1]; } } return null;}Step 4: Execute via the command line
Section titled “Step 4: Execute via the command line”Run the headless command from the repository root:
/home/soulwax/Unity/Hub/Editor/6000.6.0f1/Editor/Unity \ -batchmode -nographics -silent-crashes \ -projectPath . \ -executeMethod VoxelSandbox.Editor.Diagnostics.HeadlessMeshAudit.Run \ -logFile Logs/mesh-audit.log
# Check the shell exit code immediatelyecho "Exit Code: $?"- If
$?is0, your diagnostic passed. - If
$?is1(or non-zero), the diagnostic caught an error or threw an unhandled exception.
Triage: Parsing the log back to file:line
Section titled “Triage: Parsing the log back to file:line”When a headless run fails, do not open the 5,000-line log and scroll manually. Follow this 4-step triage sequence:
1. Check for compile errors first
Section titled “1. Check for compile errors first”Compile errors (error CSxxxx) prevent -executeMethod from ever executing:
grep -n "error CS" Logs/mesh-audit.logIf compile errors exist, the output will give exact file and line numbers:
Assets/_Game/Editor/Diagnostics/HeadlessMeshAudit.cs(24,17): error CS0103: The name 'catalogg' does not exist in the current context2. Search for runtime exceptions
Section titled “2. Search for runtime exceptions”If compilation succeeded but the method failed:
grep -n -B 1 -A 10 "Exception:" Logs/mesh-audit.logLook for the stack frame containing your code:
System.NullReferenceException: Object reference not set to an instance of an object at VoxelSandbox.Editor.Diagnostics.HeadlessMeshAudit.Run () [0x00021] in Assets/_Game/Editor/Diagnostics/HeadlessMeshAudit.cs:333. Filter on your diagnostic tag
Section titled “3. Filter on your diagnostic tag”To read only the output your harness emitted:
grep "\[diag-mesh\]" Logs/mesh-audit.logVerify
Section titled “Verify”Verify that running the harness produces:
-
Exit code
0on clean state. -
A single session log in
Logs/mesh-audit.logstarting with Unity header and ending with:[diag-mesh] Starting headless mesh audit...[diag-mesh] PASS: Audited <count> block definitions cleanly.Exiting without the bug being met
Now do your own
Section titled “Now do your own”Use this checklist whenever isolating an issue headlessly:
- Locate the Unity binary: Check
ProjectSettings/ProjectVersion.txtto match the exact editor patch version. - Create the Editor class: Place in
Assets/_Game/Editor/Diagnostics/or the owning module’sEditor/folder. - Wrap in
try/catch:- On success:
EditorApplication.Exit(0). - On exception: log the full stack trace, then
EditorApplication.Exit(1).
- On success:
- Choose appropriate batch flags:
- Always use
-batchmode -nographics -silent-crashes. - Specify
-logFile Logs/<name>.log. - Do not pass
-quitwhen the method callsEditorApplication.Exit().
- Always use
- Triage systematically: Run
grep "error CS", thengrep "Exception", thengrep "\[your-tag\]".
Pitfalls
Section titled “Pitfalls”Pitfall 1: Adding -quit alongside EditorApplication.Exit(code)
Section titled “Pitfall 1: Adding -quit alongside EditorApplication.Exit(code)”- Symptom: Bash reports random non-zero exit codes or the method is terminated mid-execution before logging results.
- Cause:
-quitinstructs Unity to shut down asynchronously as soon as the initial command buffer clears, racing with your method’s synchronous execution. - Fix: Omit
-quit. Let your method govern lifecycle viaEditorApplication.Exit().
Pitfall 2: Silently blocking on Temp/UnityLockfile
Section titled “Pitfall 2: Silently blocking on Temp/UnityLockfile”- Symptom: The terminal command hangs forever with 0% CPU usage.
- Cause: An open Unity GUI instance or a crashed previous batch run left an active lockfile in
Temp/. - Fix: Close the GUI Editor and confirm no Unity process owns the project. Do not delete a lockfile while another Editor may still be starting or running; reopen Unity only after confirming the lock is stale.
Pitfall 3: Investigating exceptions before checking compile errors
Section titled “Pitfall 3: Investigating exceptions before checking compile errors”- Symptom: Spending 20 minutes chasing an old exception in the log when the method didn’t even run.
- Cause: When C# scripts fail to compile, Unity logs the compile error and aborts without executing
-executeMethod. - Fix: Always run
grep "error CS"before searching for runtime exceptions.
Pitfall 4: Placing headless tools outside an Editor/ folder
Section titled “Pitfall 4: Placing headless tools outside an Editor/ folder”- Symptom: Standalone release build fails with
The type or namespace name 'UnityEditor' could not be found. - Cause: Scripts referencing
UnityEditororEditorApplicationwere placed in a runtime assembly. - Fix: Ensure the file resides within an
Editor/folder governed by an editor-only assembly definition ("includePlatforms": ["Editor"]).
User-contributed notes
Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works:
**bold**,*italic*,`code`, and links.Notes policy
Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked
nofollow.