test — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited test (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Respond in the user's preferred language (detect from their recent messages, or fall back to the language setting in CLAUDE.md).
Run Unity unit/integration tests and check for runtime errors.
This skill can ALWAYS run. It supports three execution backends (tykit HTTP → Editor trigger → Unity batch mode). When Unity Editor is not open, the scripts automatically fall back to batch mode. Never skip this skill with the assumption that tests "cannot run from CLI" — they can.
Unity Backend: This skill supports multiple backends. If the built-intykit_mcptools are available (unity_health,unity_console,unity_run_tests, and — new in v0.5.0 —unity_main_thread_health,unity_focus_window,unity_dismiss_dialogfor recovery), use them first. If only third-party MCP tools are available (run_testsfrom mcp-unity, ortests-runfrom Unity-MCP), use those instead of the tykit/script commands below. If no MCP tools are available, use tykit as documented here. To discover tykit commands:curl -s -X POST http://localhost:$PORT/ -d '{"command":"commands"}' -H 'Content-Type: application/json'or'{"command":"describe-commands"}'for full schemas. PORT comes fromTemp/tykit.json.
>
For tykit command usage beyond tests (scene editing, prefab workflow, runtime reflection, recovery from hangs), see shared/tykit-reference.md.Arguments: $ARGUMENTS
editmode / edit: EditMode onlyplaymode / play: PlayMode only--filter "TestName": Filter by test name (semicolon-separated for multiple)--assembly "Asm.Tests": Filter by assembly (semicolon-separated for multiple)--timeout 300: Custom timeout in secondsExamples:
/qq:test → Run EditMode + PlayMode/qq:test play → PlayMode only/qq:test editmode --filter "Health" → Filter by name/qq:test --assembly "Game.PlayerSystem.Tests" → Filter by assemblyunity-test.sh) via the plugin bin/ directory on PATH. If a bare command fails with "command not found", fall back to ${CLAUDE_PLUGIN_ROOT}/bin/<command>.python3 on macOS/Linux. On Windows (Git Bash), use python instead (python3 is not available). The bin/ wrappers handle this automatically.ps -p PID -o args= is macOS/Linux only. On Windows, use wmic process where "ProcessId=$PID" get CommandLine or tasklist.source "${CLAUDE_PLUGIN_ROOT}/scripts/platform/detect.sh" && qq_get_editor_log_path to get the correct path for the current OS.If qq-project-state.py is available, read it before choosing test scope:
qq-project-state.py --prettyInterpret the result like this:
policy_profile=core → keep the default lighterpolicy_profile=feature → normal defaultpolicy_profile=hardening → prefer the stronger defaultdefault_test_scope is the current repo's effective no-argument defaultRules:
--filter / --assembly always windefault_test_scope=editmode → run EditMode onlydefault_test_scope=all → run EditMode first, then PlayModepolicy_profileBefore using tykit, verify it's reachable and talking to the correct Unity instance. If `tykit.json` is missing, skip this entire step — the test scripts (Step 3) automatically fall back to batch mode when Unity Editor is not running.
#### 1a. Read port + PID
TYKIT_JSON="Temp/tykit.json"
if [ ! -f "$TYKIT_JSON" ]; then
echo "tykit.json not found — skipping health check, scripts will use batch mode"
# SKIP to Step 2 — batch mode does not need tykit
fi
PORT=$("${QQ_PY:-python3}" -c "import json; print(json.load(open('$TYKIT_JSON'))['port'])")
TYKIT_PID=$("${QQ_PY:-python3}" -c "import json; print(json.load(open('$TYKIT_JSON'))['pid'])")#### 1b. Verify PID is the main Unity Editor (not a Worker)
The most common port-stealing culprit is AssetImportWorker — it can overwrite tykit.json with its own PID, leaving the port pointing at a process that isn't running TykitServer.
# macOS; on Windows use: wmic process where "ProcessId=$TYKIT_PID" get CommandLine
PROC_ARGS=$(ps -p "$TYKIT_PID" -o args= 2>/dev/null || true)
if [ -z "$PROC_ARGS" ]; then
echo "PID $TYKIT_PID is dead — tykit.json is stale"
# STOP: delete stale tykit.json, ask user to reopen Unity
fi
IS_WORKER=$(echo "$PROC_ARGS" | grep -cE "AssetImportWorker|UnityPackageManager|UnityHelper" || true)
if [ "$IS_WORKER" -ne 0 ]; then
echo "PID $TYKIT_PID is a subprocess ($PROC_ARGS), not the main Unity Editor"
# STOP: ask user to restart Unity manually (never kill — risks Library corruption)
fi#### 1c. GET health check (/ping)
PING=$(curl -s --connect-timeout 3 --max-time 5 "http://localhost:$PORT/ping" 2>/dev/null) || true
if [ -z "$PING" ]; then
echo "tykit on port $PORT not responding to /ping"
# STOP: ask user to check Unity window for modal dialogs
fi#### 1d. POST health check (compile-status)
/ping responds from the listener thread without touching Unity API. A modal dialog or domain reload can block the main thread, causing POST commands to hang while /ping still works. Verify POST works:
CS=$(curl -s --connect-timeout 5 --max-time 15 -X POST "http://localhost:$PORT/" \
-d '{"command":"compile-status"}' -H 'Content-Type: application/json' 2>/dev/null) || true
if [ -z "$CS" ]; then
echo "tykit POST timed out — ping works but commands do not"
# STOP: Diagnose by priority:
# 1. Re-check PID (step 1b) — Worker is the #1 cause
# 2. Check for Unity modal dialogs blocking the main thread
# 3. Wait 30s for domain reload to finish, then retry
else
echo "tykit healthy: port=$PORT pid=$TYKIT_PID"
fiDiagnostic table:
| Symptom | Likely cause | Action |
|---|---|---|
tykit.json missing | Unity not running or never opened this project | Skip health check — scripts fall back to batch mode |
| PID dead | Unity closed but tykit.json not cleaned up | Delete stale tykit.json, ask user to reopen |
| PID is AssetImportWorker | Worker subprocess stole the port on restart | Ask user to restart Unity manually |
| PID is UnityPackageManager/UnityHelper | Other Unity subprocess inherited the port | Ask user to restart Unity manually |
/ping timeout | Unity hung or not listening | Ask user to check Unity window |
/ping OK but POST timeout | Modal dialog / stalled domain reload / package resolve blocking main thread | Call `GET /health` to confirm → run recovery flow below |
#### 1e. Recovery when main thread is blocked (tykit v0.5.0+ only)
When POST commands time out but /ping still works, the main thread is blocked. These endpoints run on the HTTP listener thread and bypass the queue, so they work even when POST commands don't:
# 1. Confirm main thread is blocked
curl -s --connect-timeout 3 --max-time 5 "http://localhost:$PORT/health"
# Look at mainThreadBlocked and hint fields
# 2. Try bringing Unity to foreground (Windows only) — fixes 90% of stalls
# (Unity background-throttles domain reload / package resolve when unfocused)
curl -s --connect-timeout 3 --max-time 5 "http://localhost:$PORT/focus-unity"
# 3. If that fails, try dismissing a modal dialog (Windows only)
curl -s --connect-timeout 3 --max-time 5 "http://localhost:$PORT/dismiss-dialog"
# 4. After recovery, retry your POST commandBuilt-in `tykit_mcp` equivalent: unity_main_thread_health, unity_focus_window, unity_dismiss_dialog MCP tools.
Third-party MCP users (`mcp-unity` / `Unity-MCP`): These recovery endpoints are tykit-specific and have no equivalent in third-party MCPs. If POST hangs, the only options are (a) manually clicking/closing the Unity window, (b) waiting for domain reload to finish, or (c) switching to tykit direct HTTP / built-in tykit_mcp.
Rules:
find_unity from unity-common.sh or let the user specifytykit.json), stop and report — do not attempt workaroundstykit.json is missing, skip to Step 2 — test scripts handle batch mode fallback automaticallyBuilt-in `tykit_mcp`: Preferunity_healthand stop if it reportsok: false.
>
Third-party MCP backends: Skip this step entirely — their tools manage their own connection.
source "${CLAUDE_PLUGIN_ROOT}/scripts/platform/detect.sh"
EDITOR_LOG="$(qq_get_editor_log_path)"
BASELINE=$(wc -l < "$EDITOR_LOG")
if [ -n "$PORT" ]; then
curl -s --connect-timeout 5 --max-time 15 -X POST http://localhost:$PORT/ \
-d '{"command":"clear-console"}' -H 'Content-Type: application/json'
fiBuilt-in `tykit_mcp`: Useunity_consolewithaction: "clear"when available.
>
Third-party MCP backends: Skip this step — neither mcp-unity nor Unity-MCP has a console-clear equivalent. Runtime error checking (Step 4) uses Editor.log directly and does not depend on console state.
Select command based on arguments:
| Argument | Command |
|---|---|
(none) + default_test_scope=all | unity-unit-test.sh |
(none) + default_test_scope=editmode | unity-test.sh editmode --timeout 180 |
editmode | unity-test.sh editmode --timeout 180 |
playmode | unity-test.sh playmode --timeout 180 |
| with filter/assembly | unity-test.sh <mode> --filter "X" --assembly "Y" --timeout Z |
default_test_scope from project stateunity-test.sh and pass all arguments throughBuilt-in `tykit_mcp`: Use unity_run_tests first. Pass mode, filter, assembly, and timeout as tool parameters. When no mode argument is given, preserve the sequencing: run EditMode first, check the result, and only proceed to PlayMode if EditMode passes. On failure, apply the same analysis as below.>
Third-party MCP backends: If the built-in bridge is not available, userun_tests(mcp-unity) ortests-run(Unity-MCP) instead of the scripts above. Pass mode, filter, assembly, and timeout as tool parameters. When no mode argument is given, preserve the sequencing: run EditMode first, check the result, and only proceed to PlayMode if EditMode passes. On failure, apply the same analysis as below.
Even if all tests pass, runtime errors may still occur. Check via Editor.log (not dependent on the console API buffer):
tail -n +$((BASELINE + 1)) "$EDITOR_LOG" | \
grep -iE "NullReferenceException|Exception:|Error\b" | \
grep -v "^UnityEngine\.\|^Cysharp\.\|^System\.Threading\.\|^ at \|CompilerError\|StackTrace" | \
sort -uShow all errors to the user — do not filter or omit any. For each error, include a source assessment (e.g., "exception from TaskEdgeCaseTests safety test, likely expected behavior"), and let the user decide whether action is needed.
After tests complete, recommend the next step:
recommended_next is /qq:doc-drift → "All green. Next up is /qq:doc-drift before shipping."recommended_next is /qq:commit-push → "All green. Ready for /qq:commit-push."<recommended_next>."/qq:test to confirm, or proceed to /qq:doc-drift?"`--auto` mode: skip asking:
qq-execute-checkpoint.py pipeline-advance --project . --completed-skill "/qq:test" --next-skill "<recommended_next>" (use the actual recommended_next from project state, not a hardcoded value), then continue with recommended_next/qq:test (max 3 attempts, then stop and ask user)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.