Skip to content

Claude NL/T Full Suite (Unity live) #29

Claude NL/T Full Suite (Unity live)

Claude NL/T Full Suite (Unity live) #29

Workflow file for this run

name: Claude NL suite + (optional) Unity compile
on:
workflow_dispatch: {}
permissions:
contents: write # allow Claude to write test artifacts
pull-requests: write # allow annotations / comments
issues: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
nl-suite:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# Python + uv for the Unity MCP server
- name: Install Python + uv
uses: astral-sh/setup-uv@v4
with:
python-version: '3.11'
- name: Install UnityMcpServer deps
run: |
set -eux
if [ -f "UnityMcpBridge/UnityMcpServer~/src/pyproject.toml" ]; then
uv venv
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV"
echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
uv pip install -e "UnityMcpBridge/UnityMcpServer~/src"
elif [ -f "UnityMcpBridge/UnityMcpServer~/src/requirements.txt" ]; then
uv venv
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV"
echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
uv pip install -r "UnityMcpBridge/UnityMcpServer~/src/requirements.txt"
else
echo "No Python deps found (skipping)"
fi
- name: Verify Python env
run: |
set -eux
which python
python -V
python -c "import mcp; print('mcp ok')"
- name: Preflight MCP modules (fail fast)
run: |
set -eux
uv run --active --directory UnityMcpBridge/UnityMcpServer~/src python - <<'PY'
import sys, pkgutil
import tools
mods = {name for _, name, _ in pkgutil.iter_modules(tools.__path__)}
required = {"manage_script","manage_script_edits","resource_tools"}
missing = required - mods
if missing:
print(f"Missing MCP tool modules: {sorted(missing)}")
sys.exit(1)
print("MCP tool modules present:", sorted(required))
PY
- name: Ensure artifact dirs exist
run: mkdir -p reports
# Seed Claude settings on-disk so CI never prompts for permissions.
# (We include both the legacy and current keys; the action will pick up what it understands.)
- name: Seed Claude settings (auto-approve MCP + filesystem tools)
run: |
set -eux
mkdir -p "$HOME/.claude"
cat > "$HOME/.claude/settings.json" <<'JSON'
{
"enableAllProjectMcpServers": true,
/* Legacy keys used by some builds */
"permissionMode": "allow",
"autoApprove": ["Bash","Write","Edit","MultiEdit","mcp__unity__*","ListMcpResourcesTool","ReadMcpResourceTool","Read","LS","Glob","Grep"],
/* Current keys used by newer builds */
"defaultMode": "bypassPermissions",
"permissionStorage": "none",
"permissions": {
"allow": [
"Read", "Write", "LS", "Glob", "Grep",
"Bash(git:*)",
"mcp__unity__*",
"ListMcpResourcesTool",
"ReadMcpResourceTool"
]
}
}
JSON
echo "Seeded settings:"
cat "$HOME/.claude/settings.json"
- name: Log MCP server location (diagnostic)
run: |
set -ux
SRV_DIR="UnityMcpBridge/UnityMcpServer~/src"
echo "MCP server dir := ${SRV_DIR}"
python - <<'PY'
import pathlib
p = pathlib.Path('UnityMcpBridge/UnityMcpServer~/src').resolve()
print('Resolved path:', p)
print('Exists:', p.exists())
print('server.py present:', (p / 'server.py').exists())
PY
ls -la "${SRV_DIR}" || true
uv --version || true
uv run --active --directory "${SRV_DIR}" python -c "import os,sys,pathlib; print('uv cwd:', os.getcwd()); print('server.py exists:', pathlib.Path('server.py').exists())" || true
- name: Run Claude startup test suite
if: success()
id: claude_startup
uses: anthropics/claude-code-base-action@beta
with:
prompt_file: .claude/prompts/nl-startuptest.md
allowed_tools: "Bash(git:*),Read,Write,LS,Glob,Grep,ListMcpResourcesTool,ReadMcpResourceTool,mcp__unity__*"
mcp_config: |
{
"mcpServers": {
"unity": {
"command": "uv",
"args": [
"run",
"--active",
"--directory",
"UnityMcpBridge/UnityMcpServer~/src",
"python",
"server.py"
],
"transport": { "type": "stdio" },
"env": { "PYTHONUNBUFFERED": "1", "MCP_LOG_LEVEL": "debug" }
}
}
}
# Force auto-approval in the runner (both legacy and current schema)
settings: |
{
"permissionMode": "allow",
"autoApprove": ["Bash","Write","Edit","MultiEdit","mcp__unity__*","ListMcpResourcesTool","ReadMcpResourceTool","Read","LS","Glob","Grep"],
"defaultMode": "bypassPermissions",
"permissionStorage": "none",
"permissions": {
"allow": [
"Read", "Write", "LS", "Glob", "Grep",
"Bash(git:*)",
"mcp__unity__*",
"ListMcpResourcesTool",
"ReadMcpResourceTool"
]
}
}
# Nudge the agent to use the correct base dir for this repo (no Assets/)
append_system_prompt: |
IMPORTANT: This repository root does not contain an Assets/ folder.
When using mcp__unity__list_resources or read_resource, use under: "." or under: "ClaudeTests".
Do NOT rely on ListMcpResourcesTool; prefer the Unity-specific tools.
model: "claude-3-7-sonnet-20250219"
max_turns: "12"
timeout_minutes: "10"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Run Claude NL/T test suite
if: steps.claude_startup.outcome == 'success'
id: claude
uses: anthropics/claude-code-base-action@beta
with:
prompt_file: .claude/prompts/nl-unity-suite.md
allowed_tools: "Bash(git:*),Read,Write,LS,Glob,Grep,ListMcpResourcesTool,ReadMcpResourceTool,mcp__unity__*"
mcp_config: |
{
"mcpServers": {
"unity": {
"command": "uv",
"args": [
"run",
"--active",
"--directory",
"UnityMcpBridge/UnityMcpServer~/src",
"python",
"server.py"
],
"transport": { "type": "stdio" },
"env": { "PYTHONUNBUFFERED": "1", "MCP_LOG_LEVEL": "debug" }
}
}
}
settings: |
{
"permissionMode": "allow",
"autoApprove": ["Bash","Write","Edit","MultiEdit","mcp__unity__*","ListMcpResourcesTool","ReadMcpResourceTool","Read","LS","Glob","Grep"],
"defaultMode": "bypassPermissions",
"permissionStorage": "none",
"permissions": {
"allow": [
"Read", "Write", "LS", "Glob", "Grep",
"Bash(git:*)",
"mcp__unity__*",
"ListMcpResourcesTool",
"ReadMcpResourceTool"
]
}
}
append_system_prompt: |
IMPORTANT: The workspace is not a Unity project; there is no Assets/ directory.
Use mcp__unity__list_resources with under: "." (or "ClaudeTests") and pattern: "*" to discover files.
model: "claude-3-7-sonnet-20250219"
max_turns: "20"
timeout_minutes: "20"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Mark permission issues as skipped in JUnit
if: always()
run: |
python .github/scripts/mark_skipped.py reports/claude-nl-tests.xml
- name: Ensure JUnit exists (fallback)
if: always()
run: |
set -eux
mkdir -p reports
if [ ! -f reports/claude-nl-tests.xml ]; then
printf '%s\n' \
'<testsuites>' \
' <testsuite name="UnityMCP.NL" tests="1" failures="1" errors="0" skipped="0" time="0.0">' \
' <testcase name="Bootstrap" classname="UnityMCP.NL" time="0.0">' \
' <failure message="No MCP resources detected by aggregator or test aborted early">' \
' Claude ran but the startup checks failed to detect usable MCP resources; NL/T flow did not execute.' \
' </failure>' \
' </testcase>' \
' </testsuite>' \
'</testsuites>' \
> reports/claude-nl-tests.xml
fi
- name: Upload JUnit (Claude NL/T)
if: always()
uses: actions/upload-artifact@v4
with:
name: claude-nl-tests
path: reports/claude-nl-tests.xml
if-no-files-found: ignore
- name: Annotate PR with test results (Claude NL/T)
if: always()
uses: dorny/test-reporter@v1
with:
name: Claude NL/T
path: reports/claude-nl-tests.xml
reporter: java-junit
fail-on-empty: false
# Detect secrets + project/package mode WITHOUT using secrets in `if:`
- name: Detect Unity mode & secrets
id: detect
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
run: |
if [ -n "$UNITY_LICENSE" ]; then echo "has_license=true" >> "$GITHUB_OUTPUT"; else echo "has_license=false" >> "$GITHUB_OUTPUT"; fi
if [ -f "ProjectSettings/ProjectVersion.txt" ]; then echo "is_project=true" >> "$GITHUB_OUTPUT"; else echo "is_project=false" >> "$GITHUB_OUTPUT"; fi
if [ -f "Packages/manifest.json" ] && [ ! -f "ProjectSettings/ProjectVersion.txt" ]; then echo "is_package=true" >> "$GITHUB_OUTPUT"; else echo "is_package=false" >> "$GITHUB_OUTPUT"; fi
# --- Optional: Unity compile after Claude’s edits (satisfies NL-4) ---
- name: Unity compile (Project)
if: always() && steps.detect.outputs.has_license == 'true' && steps.detect.outputs.is_project == 'true'
uses: game-ci/unity-test-runner@v4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with:
projectPath: .
githubToken: ${{ secrets.GITHUB_TOKEN }}
testMode: EditMode
- name: Unity compile (Package)
if: always() && steps.detect.outputs.has_license == 'true' && steps.detect.outputs.is_package == 'true'
uses: game-ci/unity-test-runner@v4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with:
packageMode: true
unityVersion: 2022.3.45f1
projectPath: .
githubToken: ${{ secrets.GITHUB_TOKEN }}
- name: Clean working tree (discard temp edits)
if: always()
run: |
git restore -SW :/
git clean -fd