Claude NL/T Full Suite (Unity live) #29
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |