Skip to main content

Local debugging guide for Extend Service Extension

Last updated on March 20, 2026

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:

PortPurpose
6565gRPC server (internal — used by gRPC-Gateway)
8000gRPC-Gateway HTTP/REST — call this from a browser, Postman, or curl
8080Prometheus metrics endpoint (/metrics)

When something goes wrong, trace the problem layer by layer:

SymptomMost likely layerWhere to look first
Service won't startEnvironment / credentials.env file, IAM client credentials
HTTP 4xx responsesAuth interceptorservice.proto permission definitions
HTTP 5xx responsesBusiness logic / storageService and storage implementation files
Debugger never pausesPort conflict or build modeCheck running processes on ports 6565/8000
Proto changes ignoredGenerated code staleRegenerate 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.

VS Code shortcut

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
VariablePurposeExample
AB_BASE_URLYour AGS environment URLhttps://dev.accelbyte.io
AB_CLIENT_IDOAuth client IDabc123
AB_CLIENT_SECRETOAuth client secrets3cr3t
BASE_PATHURL prefix (must start with /)/guild
PLUGIN_GRPC_SERVER_AUTH_ENABLEDfalse to skip token checks locallyfalse
LOG_LEVELLog verbositydebug

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.

  1. Make sure your .env file is filled in.
  2. Open the Run and Debug panel — press Ctrl+Shift+D (Windows/Linux) or Cmd+Shift+D (macOS), or click the bug icon in the Activity Bar.
  3. Select "Debug: Service" from the dropdown at the top of the panel.
  4. 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

ActionShortcutWhat it does
ContinueF5Run until the next breakpoint
Step OverF10Execute the current line without entering any function it calls
Step IntoF11Enter the function called on the current line
Step OutShift+F11Finish the current function and return to the caller
RestartCtrl+Shift+F5Restart the debug session
StopShift+F5Stop 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

LevelWhen it appearsWhen to pay attention
DEBUGOnly when LOG_LEVEL=debugFine-grained tracing of logic and variable values
INFODefaultNormal events (server started, request received)
WARNSomething unexpected but non-fatalWorth investigating if it appears repeatedly
ERRORSomething failedAlways 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:

  1. Double-check the credentials in your .env.
  2. Confirm AB_BASE_URL is reachable: curl "$AB_BASE_URL/iam/v3/public/config".
  3. Make sure your OAuth client has the CLIENT_CREDENTIALS grant 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:

  1. Check the terminal output for an ERROR log around the time of the request.
  2. Set a breakpoint inside the failing service method.
  3. Step into the storage calls to check whether CloudSave is returning an error.
  4. Verify the namespace in 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:

CauseFix
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 requestCheck 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

Optional section

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:

LanguageSKILL.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:

IntentWhat 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 decideDescribe the problem naturally — the AI picks the right mode
Compatibility

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. The GetGuildProgress method returns codes.Internal whenever the guild doesn't exist yet. Should I handle NotFound separately?"

Ask for an explanation before a fix.

"Explain what PLUGIN_GRPC_SERVER_AUTH_ENABLED does 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 GetGuildProgress that covers the case where CloudSave returns a not-found error. Use the mocks in pkg/service/mocks/."


Tips and best practices

  • Always use LOG_LEVEL=debug locally. 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=false is a development shortcut — not a permanent setting. Before pushing, set it back to true and 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 guildId or namespace).

  • 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, or len(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 .proto change. Editing service.proto without running ./proto.sh afterwards is one of the most common sources of confusing compile or runtime errors.

  • Never commit your .env file. It contains credentials. Confirm it is listed in .gitignore. Do commit changes to .env.template so 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:


References