Local debugging guide for Extend Service Extension
Overview
This guide walks you through debugging an Extend Service Extension app on your local machine.
The guide focuses on Visual Studio Code (VS Code), but the concepts apply to any editor or IDE.
For language-specific setup — prerequisites, project structure, run commands, and debugger configuration — jump straight to your language:
How a request flows through the service
Every Extend Service Extension app is built on the same layered stack regardless of language. Understanding this helps you narrow down where a problem originates:
Game Client / AGS
│ HTTP (REST)
▼
gRPC-Gateway (port 8000) ← translates HTTP ↔ gRPC
│ gRPC
▼
gRPC Server (port 6565) ← your business logic lives here
│
▼
AccelByte CloudSave / other AGS services
Port summary:
| Port | Purpose |
|---|---|
6565 | gRPC server (internal — used by gRPC-Gateway) |
8000 | gRPC-Gateway HTTP/REST — call this from a browser, Postman, or curl |
8080 | Prometheus metrics endpoint (/metrics) |
When something goes wrong, trace the problem layer by layer:
| Symptom | Most likely layer | Where to look first |
|---|---|---|
| Service won't start | Environment / credentials | .env file, IAM client credentials |
| HTTP 4xx responses | Auth interceptor | service.proto permission definitions |
| HTTP 5xx responses | Business logic / storage | Service and storage implementation files |
| Debugger never pauses | Port conflict or build mode | Check running processes on ports 6565/8000 |
| Proto changes ignored | Generated code stale | Regenerate protobuf bindings |
Environment setup
The service reads all configuration from environment variables. A template file is provided in the repository root so you know exactly which variables to fill in.
Step 1 — Create your .env file
cp .env.template .env
Then open .env in your editor and fill in the values.
The repository ships with a VS Code task called "Create .env File" (see .vscode/tasks.json).
If you have fillenv installed, this task can populate the file for you automatically.
Step 2 — Fill in the required variables
The critical variables are the same across all languages:
# AccelByte environment
AB_BASE_URL=https://<your-ags-environment>.accelbyte.io
AB_CLIENT_ID=<your-client-id>
AB_CLIENT_SECRET=<your-client-secret>
# URL prefix this service is served under. Must start with /.
BASE_PATH=/guild
# Disable token validation during local development.
# Only works for local development.
# Deployed Extend apps always have this value set to `true` and can't be changed.
PLUGIN_GRPC_SERVER_AUTH_ENABLED=false
# Log verbosity: debug | info | warn | error
LOG_LEVEL=debug
| Variable | Purpose | Example |
|---|---|---|
AB_BASE_URL | Your AGS environment URL | https://dev.accelbyte.io |
AB_CLIENT_ID | OAuth client ID | abc123 |
AB_CLIENT_SECRET | OAuth client secret | s3cr3t |
BASE_PATH | URL prefix (must start with /) | /guild |
PLUGIN_GRPC_SERVER_AUTH_ENABLED | false to skip token checks locally | false |
LOG_LEVEL | Log verbosity | debug |
Step 3 — Confirm the service can reach AGS
Run a quick check to verify your base URL and credentials before starting the service:
curl "$AB_BASE_URL/iam/v3/public/config"
You should get a JSON response. An error here means the URL is wrong or unreachable.
Running the service locally
From VS Code
Use Terminal → Run Task → "Run: Service".
This task is defined in .vscode/tasks.json and loads the .env file for you automatically.
Confirming the service is up
Once the service starts successfully you should see log output similar to:
{"time":"...","level":"INFO","msg":"app server started","service":"extend-app-service-extension"}
{"time":"...","level":"INFO","msg":"starting gRPC-Gateway HTTP server","port":8000}
{"time":"...","level":"INFO","msg":"serving prometheus metrics","port":8080,"endpoint":"/metrics"}
Open http://localhost:8000<BASE_PATH>/apidocs/ in your browser. If Swagger UI appears and
lists your endpoints, the service is running correctly.
For the exact terminal command for your language, see the language-specific guide.
Attaching the debugger in VS Code
The repository ships with a ready-to-use launch configuration in .vscode/launch.json.
- Make sure your
.envfile is filled in. - Open the Run and Debug panel — press
Ctrl+Shift+D(Windows/Linux) orCmd+Shift+D(macOS), or click the bug icon in the Activity Bar. - Select "Debug: Service" from the dropdown at the top of the panel.
- Press F5 (or click the green ▶ button).
VS Code compiles the service with debug symbols and starts it inside the debugger. The service behaves exactly like a normal run, but you can now pause execution, inspect variables, and step through code line by line.
For the language-specific launch configuration details and non-VS Code setups, see the language-specific guide.
Setting breakpoints and inspecting state
How to set a breakpoint
Click in the gutter — the narrow strip to the left of the line numbers — next to the line you want to pause on. A red circle appears. When execution reaches that line, VS Code pauses and shows you:
- Variables — all local variables and their current values.
- Watch — expressions you add manually to monitor continuously.
- Call Stack — every function call that led to the current line.
- Debug Console — a prompt where you can evaluate expressions live.
Stepping through code
| Action | Shortcut | What it does |
|---|---|---|
| Continue | F5 | Run until the next breakpoint |
| Step Over | F10 | Execute the current line without entering any function it calls |
| Step Into | F11 | Enter the function called on the current line |
| Step Out | Shift+F11 | Finish the current function and return to the caller |
| Restart | Ctrl+Shift+F5 | Restart the debug session |
| Stop | Shift+F5 | Stop the debugger |
Conditional breakpoints
Right-click a breakpoint circle → Edit Breakpoint → type a condition.
The debugger will only pause when the condition evaluates to true.
This is especially useful when you want to inspect only a specific request out of many.
For condition syntax examples in each language, see the language-specific guide.
Reading and understanding logs
The service emits structured JSON log lines to stdout. A single log line looks like this:
{"time":"2026-03-10T12:00:00Z","level":"INFO","msg":"HTTP request","method":"POST","path":"/guild/v1/admin/namespace/mygame/progress","duration":"1.234ms"}
Log levels
| Level | When it appears | When to pay attention |
|---|---|---|
DEBUG | Only when LOG_LEVEL=debug | Fine-grained tracing of logic and variable values |
INFO | Default | Normal events (server started, request received) |
WARN | Something unexpected but non-fatal | Worth investigating if it appears repeatedly |
ERROR | Something failed | Always investigate |
Always set LOG_LEVEL=debug in your .env during local development.
gRPC request/response pairs
The service automatically logs the start and finish of every gRPC call. When you send an HTTP request to port 8000, look for pairs like:
{"msg":"started call","grpc.method":"CreateOrUpdateGuildProgress", ...}
{"msg":"finished call","grpc.code":"OK","grpc.duration":"2ms", ...}
If grpc.code is anything other than OK — for example Unauthenticated, Internal, or
NotFound — scroll up in the logs to find the ERROR line that explains why.
Pretty-printing logs with jq
Raw JSON is compact and hard to read quickly. Install jq and pipe the service output through it during local development:
# Pretty-print all logs (example using Go; substitute your language's run command)
go run main.go 2>&1 | jq '.'
# Show only ERROR-level logs
go run main.go 2>&1 | jq 'select(.level == "ERROR")'
Common issues
Service fails to start — BASE_PATH not set
Symptom:
{"level":"ERROR","msg":"BASE_PATH envar is not set or empty"}
Cause: The BASE_PATH environment variable is missing or does not start with /.
Fix: Add BASE_PATH=/guild (or any path starting with /) to your .env and restart.
Service exits — "unable to login using clientId and clientSecret"
Symptom:
{"level":"ERROR","msg":"error unable to login using clientId and clientSecret","error":"..."}
Cause: AB_CLIENT_ID or AB_CLIENT_SECRET is wrong, or AB_BASE_URL points to the wrong
environment.
Fix:
- Double-check the credentials in your
.env. - Confirm
AB_BASE_URLis reachable:curl "$AB_BASE_URL/iam/v3/public/config". - Make sure your OAuth client has the
CLIENT_CREDENTIALSgrant type enabled in the Admin Portal.
All requests return 401 Unauthenticated
Symptom: Every HTTP call to port 8000 returns HTTP 401.
Cause: Token validation is enabled and the bearer token in your request is missing, expired,
or lacks the permission required by the endpoint (defined in service.proto).
Fix (local debugging): Set PLUGIN_GRPC_SERVER_AUTH_ENABLED=false in .env and restart.
Fix (real token): Use a valid AGS token with the required permission. Check service.proto
for the exact permission.resource and permission.action each endpoint requires.
Endpoint returns 500 Internal Server Error
Symptom: HTTP 500 or gRPC status Internal.
How to diagnose:
- Check the terminal output for an
ERRORlog around the time of the request. - Set a breakpoint inside the failing service method.
- Step into the storage calls to check whether CloudSave is returning an error.
- Verify the
namespacein your request matches a real namespace in your AGS environment.
Breakpoints are never hit
Symptoms: You set a breakpoint but the debugger never pauses there.
Possible causes and fixes:
| Cause | Fix |
|---|---|
| The request fails before reaching your code (e.g., at auth) | Disable auth with PLUGIN_GRPC_SERVER_AUTH_ENABLED=false |
| Another instance of the service is running and handling the request | Check for port conflicts: ss -tlnp | grep -E '6565|8000' and stop the stale process |
| The code path is not reached (wrong endpoint URL) | Confirm the URL matches a real endpoint in service.proto |
Proto changes have no effect
Symptom: You edited service.proto but the behavior of the service did not change.
Cause: The generated code stubs have not been regenerated.
Fix: Run the "Proto: Generate" VS Code task, or run ./proto.sh directly, then restart
the service.
Testing endpoints manually
Swagger UI (easiest)
Navigate to http://localhost:8000<BASE_PATH>/apidocs/ in your browser.
The built-in Swagger UI lets you read the API documentation and send requests directly from
the browser without any extra setup.
curl
# Example: create or update guild progress
curl -s -X POST \
"http://localhost:8000/guild/v1/admin/namespace/mygame/progress" \
-H "Content-Type: application/json" \
-d '{"guildProgress": {"guildId": "guild_001", "namespace": "mygame"}}' | jq .
When PLUGIN_GRPC_SERVER_AUTH_ENABLED=true, add -H "Authorization: Bearer <your-token>" to
every request.
Postman
The demo/ directory in the repository contains Postman collection files
(*.postman_collection.json) with pre-built requests. Import them into Postman, then update
the environment variables — baseUrl, namespace, and token — to match your local setup.
grpcurl (call the gRPC layer directly)
grpcurl lets you call the gRPC server on port 6565 directly, bypassing the HTTP gateway.
This is useful to isolate whether a problem is in the gateway layer or the gRPC layer.
# List available services (server reflection is enabled)
grpcurl -plaintext localhost:6565 list
# Call a method directly
grpcurl -plaintext \
-d '{"namespace":"mygame","guildProgress":{"guildId":"guild_001"}}' \
localhost:6565 service.Service/CreateOrUpdateGuildProgress
Debugging with AI assistance
If your team does not use AI tooling, or if you are discouraged from using AI at work, skip this section. Every other section in this guide is fully self-contained and does not require an AI assistant.
AI coding assistants such as Claude Code, GitHub Copilot, and others can act as a debugging companion — explaining unfamiliar code, parsing logs, and suggesting targeted fixes.
Using the debugging skill
The Extend Service Extension app template ships with a ready-made agent skill at
.claude/skills/debugging-guide/SKILL.md. A skill is a set of instructions that tells the AI
exactly how to help with a specific domain — in this case, debugging Extend apps.
The skill file is available in each language's template repository:
| Language | SKILL.md |
|---|---|
| Go | .claude/skills/debugging-guide/SKILL.md |
| C# | .claude/skills/debugging-guide/SKILL.md |
| Java | .claude/skills/debugging-guide/SKILL.md |
| Python | .claude/skills/debugging-guide/SKILL.md |
Once the skill is active, invoke it by typing one of the following in your AI chat:
| Intent | What to type |
|---|---|
| Debug a live issue | /debugging-guide Go — getting 500 on GetGuildProgress |
| Write or update the debugging guide | /debugging-guide write Go |
| Let the AI decide | Describe the problem naturally — the AI picks the right mode |
Agent skills work out of the box with Claude Code. For other AI tools or IDE extensions,
check whether they support the Agent Skills open standard.
If your tool does not support skills, copy the contents of .claude/skills/debugging-guide/SKILL.md
and paste it as the first message (or system prompt) in your chat session.
Below is the full skill file for reference. You can copy and save this as
.claude/skills/debugging-guide/SKILL.md in your own Extend app repository.
View the full debugging skill (SKILL.md)
---
name: debugging-guide
description: >
Expert guide writer and debugging assistant for AccelByte Extend Service Extension apps.
Use when a developer asks for help debugging their Extend service, diagnosing startup or
runtime errors, understanding logs, setting up a debugger, or when writing or updating a
DEBUGGING_GUIDE.md for an Extend app. Covers Go but the workflow is applicable to other
supported languages (Python, C#, Java).
argument-hint: "[language] [brief issue description or 'write guide']"
allowed-tools: Read, Grep, Glob, Bash(go *), Bash(dlv *), Bash(ss *), Bash(curl *), Bash(grpcurl *), Bash(jq *)
---
# Debugging Guide Skill — Extend Service Extension
You are an expert backend developer and technical writer specializing in AccelByte Gaming
Services (AGS) Extend apps. Your two modes of operation are:
1. **Debug Mode** — Help a developer diagnose and fix a real issue in their running service.
2. **Write Mode** — Author or update a `DEBUGGING_GUIDE.md` for an Extend Service Extension repository.
Detect which mode is needed from `$ARGUMENTS`. If the argument mentions a specific error,
log output, or symptom, use Debug Mode. If it mentions "write", "guide", or "document", use
Write Mode. If ambiguous, ask one clarifying question: *"Do you want help debugging a live
issue, or do you want me to write/update the debugging guide?"*
---
## Architecture Context
Every Extend Service Extension app shares this layered architecture:
\`\`\`
Game Client / AGS
│ HTTP (REST)
▼
gRPC-Gateway (port 8000) ← translates HTTP ↔ gRPC
│ gRPC
▼
gRPC Server (port 6565) ← business logic lives here
│
▼
AccelByte CloudSave / other AGS services
\`\`\`
Key environment variables:
| Variable | Purpose |
|---|---|
| `AB_BASE_URL` | AccelByte environment base URL |
| `AB_CLIENT_ID` / `AB_CLIENT_SECRET` | OAuth client credentials |
| `BASE_PATH` | URL prefix (must start with `/`) |
| `PLUGIN_GRPC_SERVER_AUTH_ENABLED` | `false` to skip IAM token validation locally |
| `LOG_LEVEL` | `debug` \| `info` \| `warn` \| `error` |
---
## Debug Mode
### Step 1 — Identify the layer where the failure occurs
- Service fails to **start** → environment variables and IAM login.
- Returns **4xx** → auth interceptor and proto-defined permissions.
- Returns **5xx** → business logic and storage layer.
- Debugger **won't pause** → port conflicts or build mode.
- **Proto changes ignored** → regenerate bindings.
### Step 2 — Collect evidence
1. Ask for full log output at `LOG_LEVEL=debug`. Look for `"level":"ERROR"` lines.
2. Read the source files referenced in the error before suggesting a fix.
3. Check environment: `printenv | grep -E 'AB_|BASE_PATH|PLUGIN_GRPC|LOG_LEVEL'`
4. Check ports: `ss -tlnp | grep -E '6565|8000|8080'`
### Step 3 — Common-issue checklist
| Symptom | Likely cause | Where to look |
|---|---|---|
| `BASE_PATH envar is not set or empty` | Missing/invalid `BASE_PATH` | `main.go` startup |
| `unable to login using clientId and clientSecret` | Wrong credentials or unreachable `AB_BASE_URL` | `main.go` → OAuth login |
| All requests return `401 Unauthenticated` | Token missing/expired or wrong permission | Auth interceptor; `service.proto` |
| `500 Internal Server Error` | CloudSave call failed or data parse error | Storage and service files |
| Breakpoints never hit | Wrong port, auth failure before breakpoint | Disable auth, check port conflicts |
| Proto changes have no effect | Generated code not regenerated | Run `./proto.sh` |
### Step 4 — Suggest a minimal fix
- Explain *why* the fix works.
- Read the file first, then show the exact change.
- Provide a verification step after every fix.
### Step 5 — Verify
\`\`\`bash
# Confirm service starts cleanly
go run main.go 2>&1 | jq 'select(.level == "ERROR")'
# Confirm endpoint responds
curl -s http://localhost:8000/guild/v1/admin/namespace/mygame/progress | jq .
# Confirm gRPC layer directly
grpcurl -plaintext localhost:6565 list
\`\`\`
---
## Write Mode
### Audience
Junior developers and game developers with limited backend experience. Avoid assuming knowledge
of gRPC, protobuf, or IAM. Use plain language. VS Code-centric but include notes for other IDEs.
### Required sections
1. Overview + architecture diagram
2. Project structure reference (file-to-responsibility table, port table)
3. Prerequisites (language-specific)
4. Environment setup (.env, key variables, why to disable auth locally)
5. Running the service (terminal + VS Code task)
6. Attaching the debugger (VS Code launch config, other IDEs/headless)
7. Breakpoints and inspection (placement table, stepping shortcuts, conditional breakpoints)
8. Reading logs (format, levels, gRPC pairs, jq)
9. Common issues (symptom / cause / fix per issue)
10. Testing endpoints manually (Swagger UI, curl, Postman, grpcurl)
11. AI-assisted debugging (this skill, MCP servers, prompting tips)
12. Tips and best practices
### Before writing, always read first
1. Existing `DEBUGGING_GUIDE.md` if present — update, do not replace accurate content.
2. `main.go` (or equivalent entry point) for ports, startup sequence, and env vars.
3. Service and storage files to understand the business logic layer.
4. `.vscode/launch.json` for the debug configuration name and settings.
5. `.vscode/mcp.json` for configured MCP servers.
6. `service.proto` for endpoint names and permission requirements.
MCP servers
The app template ships with two Model Context Protocol (MCP) server configurations in
.vscode/mcp.json. These give AI assistants direct access to AccelByte-specific knowledge:
| Server | What it provides |
|---|---|---|
| (ags-extend-sdk-mcp-server)[https://github.com/AccelByte/ags-extend-sdk-mcp-server] | Knowledge of AccelByte Extend SDK symbols, types, and usage patterns |
| (ags-api-mcp-server)[https://github.com/AccelByte/ags-api-mcp-server] | Live AGS REST API access (requires AB_BASE_URL in your environment) |
Effective prompting tips
The better your prompt, the better the AI's answer. A few practices that make a real difference:
Paste the full error, not just the symptom.
Instead of: "Why does my service crash?"
Try: "My Extend Service Extension exits at startup with the following log. What is the most likely cause and how do I fix it?"
{"level":"ERROR","msg":"error unable to login using clientId and clientSecret","error":"401 Unauthorized"}
Include the relevant source file.
"Here is my
pkg/service/myService.go. TheGetGuildProgressmethod returnscodes.Internalwhenever the guild doesn't exist yet. Should I handleNotFoundseparately?"
Ask for an explanation before a fix.
"Explain what
PLUGIN_GRPC_SERVER_AUTH_ENABLEDdoes and why it is safe to disable during local development."
Paste logs and ask for a summary.
"Here are 30 lines of JSON logs from my Extend Service. Which request failed, and what caused it?"
Ask for a targeted test once you understand the bug.
"Write a Go unit test for
GetGuildProgressthat covers the case where CloudSave returns a not-found error. Use the mocks inpkg/service/mocks/."
Tips and best practices
-
Always use
LOG_LEVEL=debuglocally. The extra gRPC payload logs often reveal the problem without needing a breakpoint at all. -
Disable auth locally; re-enable it before you commit.
PLUGIN_GRPC_SERVER_AUTH_ENABLED=falseis a development shortcut — not a permanent setting. Before pushing, set it back totrueand verify your token permissions still work. -
Use conditional breakpoints. Rather than stopping on every request, narrow the condition to the exact input you are investigating (for example, a specific
guildIdornamespace). -
Read the Call Stack panel. When the debugger pauses, the Call Stack shows every function call that led to the current line — starting from the top (current frame) down to the entry point. This tells you exactly how a request arrived at your code.
-
Use the Watch panel. Add expressions like
req.Namespace,err, orlen(guildProgress.Objectives)so you can monitor them as you step, without re-expanding the Variables panel at every line. -
Check ports before starting. If you see "address already in use", a previous instance of the service is still running:
ss -tlnp | grep -E '6565|8000|8080'Kill the stale process and start again.
-
Regenerate proto bindings after every
.protochange. Editingservice.protowithout running./proto.shafterwards is one of the most common sources of confusing compile or runtime errors. -
Never commit your
.envfile. It contains credentials. Confirm it is listed in.gitignore. Do commit changes to.env.templateso teammates know what variables are needed.
Language-specific guides
The sections above cover concepts and workflows that apply to all languages. For setup instructions, project structure, run commands, debugger configuration, and language-specific troubleshooting, see the dedicated guide for your language: