Local debugging guide for Extend Event Handler
The examples and file references in this guide are based on the AccelByte Extend Event Handler app templates. If you built your own service from scratch, the concepts still apply — adjust file paths, task names, and script names to match your project.
Overview
This guide walks you through debugging an Extend Event Handler app on your local machine.
For debugging concepts shared across all Extend app types — environment variable setup, VS Code debug workflow, log reading, and common startup errors — see the Extend local debugging guide.
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 an event flows through the service
Every Extend Event Handler app is built on the same event-driven stack regardless of language. Understanding this helps you narrow down where a problem originates:
AGS User Action (e.g., login)
│
▼
AGS Kafka Topic ← AGS publishes domain events here automatically
│
▼
Kafka Connect ← bridges Kafka to gRPC; managed by AGS infrastructure
│ gRPC
▼
gRPC Server (port 6565) ← your event handler logic lives here
│
▼
AccelByte AGS Services ← your handler calls back into AGS (e.g., grant an item)
Key difference from Extend Service Extension: There is no HTTP gateway in front of your
service. Events arrive exclusively via Kafka Connect over gRPC. You cannot trigger your
handler with a browser or curl the way you would a REST endpoint — instead you use
grpcurl to simulate incoming events, or trigger the real event in AGS (e.g., log in as
a test user).
Port summary:
| Port | Purpose |
|---|---|
6565 | gRPC server — receives events from Kafka Connect and accepts grpcurl test calls |
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, ITEM_ID_TO_GRANT, IAM client credentials |
| Handler is never called | Kafka Connect not configured or not running | AGS Kafka Connect configuration, port 6565 reachability |
| Handler is called but returns an error | Business logic / AGS API call | Handler files, entitlement / fulfillment calls |
| AGS API calls fail | Wrong namespace, insufficient permissions, or bad credentials | AB_NAMESPACE, IAM client permissions |
| Debugger never pauses | Port conflict or build mode | Check running processes on port 6565 |
| Proto changes ignored | Generated code stale | Regenerate protobuf bindings |
Environment setup
For the common variables (AB_BASE_URL, AB_CLIENT_ID, AB_CLIENT_SECRET,
PLUGIN_GRPC_SERVER_AUTH_ENABLED, LOG_LEVEL) and how to create and verify your .env file,
see Environment setup in the generic guide.
Extend Event Handler-specific variables
Extend Event Handler requires two additional environment variables:
# The game namespace to operate in.
AB_NAMESPACE=<your-namespace-id>
# The in-game item ID that will be granted when a user logs in.
# Must exist in a published store in the namespace above.
ITEM_ID_TO_GRANT=<item-id-from-published-store>
| Variable | Purpose | Example |
|---|---|---|
AB_NAMESPACE | The game namespace to operate in | mygame |
ITEM_ID_TO_GRANT | Item ID to grant on login event | item_abc123 |
Running the service locally
For VS Code task and terminal run instructions, see Running the service locally in the generic guide.
Confirming the service is up
Once the service starts successfully you should see log output similar to:
{"time":"...","level":"INFO","msg":"starting app server..","service":"extend-app-event-handler"}
{"time":"...","level":"INFO","msg":"gRPC reflection enabled"}
{"time":"...","level":"INFO","msg":"serving prometheus metrics","port":8080,"endpoint":"/metrics"}
{"time":"...","level":"INFO","msg":"gRPC server started"}
{"time":"...","level":"INFO","msg":"app server started"}
For the exact terminal command for your language, see the language-specific guide.
Attaching the debugger
For VS Code debugger attachment steps, see Attaching the debugger in VS Code in the generic guide.
For the Event Handler-specific .vscode/launch.json configuration and non-VS Code setups, see the
language-specific guide.
Setting breakpoints and inspecting state
For how to set breakpoints, use the VS Code debug panels, step through code, and write conditional breakpoints, see Setting breakpoints and inspecting state in the generic guide.
For the recommended breakpoint locations in your Event Handler service files and language-specific conditional syntax examples, see the language-specific guide.
Triggering events for testing
Because the Event Handler receives events passively — there is no URL to POST to — you need a way to send a test event to your locally running service. You have two options:
Option 1 — Trigger a real event in AGS
Log in as a test user in your AGS environment (for example, via the Player Portal or the
AGS game client SDK). If Kafka Connect is configured to forward events to your local service,
the userLoggedIn event arrives in your handler automatically.
When running entirely locally (without Kafka Connect pointed at your service), events from AGS will not arrive automatically. Use Option 2 to simulate them.
Option 2 — Simulate an event with grpcurl
grpcurl is a command-line tool that lets you call gRPC services directly.
Because gRPC reflection is enabled, no additional setup is required.
# Simulate a userLoggedIn event
grpcurl -plaintext \
-d '{"userId": "test-user-001", "namespace": "mygame"}' \
localhost:6565 \
accelbyte.iam.account.v1.UserAuthenticationUserLoggedInService/OnMessage
# Simulate a userThirdPartyLoggedIn event
grpcurl -plaintext \
-d '{"userId": "test-user-001", "namespace": "mygame", "platformId": "steam"}' \
localhost:6565 \
accelbyte.iam.account.v1.UserAuthenticationUserThirdPartyLoggedInService/OnMessage
A successful response is {} (an empty JSON object, which is what google.protobuf.Empty
looks like over the wire). Any non-empty error response means your handler returned an error.
Reading and understanding logs
For the structured JSON log format, log levels, gRPC call pairs, and jq usage, see
Reading and understanding logs
in the generic guide.
For the language-specific jq pipe command, see the language-specific guide.
Tracing a single event end-to-end
Every OnMessage call attaches a traceID field to every log line it emits. After sending
a grpcurl request, filter by that trace ID to see all log lines from that one event:
# Replace <your-run-command> with the language-specific command from the language guide
<your-run-command> 2>&1 | jq -r 'select(.traceID != null) | "\(.traceID) \(.level) \(.msg)"'
Common issues
For common issues that apply to all Extend apps — credential errors, 401 Unauthenticated,
and breakpoints that are never hit — see
Common issues in the generic guide.
The issues below are specific to Extend Event Handler.
Service fails to start — ITEM_ID_TO_GRANT not set
Symptom:
{"level":"ERROR","msg":"ITEM_ID_TO_GRANT environment variable is required"}
Cause: The ITEM_ID_TO_GRANT environment variable is missing from the .env file.
Fix: Add ITEM_ID_TO_GRANT=<your-item-id> to .env and restart. The item ID must
exist in a published store in the namespace specified by AB_NAMESPACE.
Handler is called but entitlement grant fails
Symptom:
{"level":"ERROR","msg":"finished call","grpc.code":"Internal","error":"failed to grant entitlement: ..."}
Possible causes and fixes:
| Cause | Fix |
|---|---|
ITEM_ID_TO_GRANT does not exist in a published store | Create and publish an item with that ID in the AGS Admin Portal |
AB_NAMESPACE does not match the namespace in the test event | Make sure AB_NAMESPACE matches the namespace field in your grpcurl payload |
| IAM client missing fulfillment permission | Add ADMIN:NAMESPACE:{namespace}:USER:*:FULFILLMENT [CREATE] to the OAuth client |
userId in the event does not exist in AGS | Use a real user ID from the correct namespace |
Handler is never invoked for real events
Symptom: A user logs in to AGS but OnMessage is never called.
Cause: Kafka Connect is not configured to forward events to your local service, or your service is not reachable from the Kafka Connect host.
Fix:
- During local development, use
grpcurl(Option 2 above) to simulate events directly instead of relying on Kafka Connect. - To receive real events, ensure Kafka Connect is configured with your service's address
(host and port 6565) and that your machine is reachable from Kafka Connect. In a typical
local setup, expose your service with a tunneling tool such as
ngrok.
Proto changes have no effect
Symptom: You edited a .proto file 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.
Debugging with AI assistance
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 Event Handler 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 |
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 Internal error on OnMessage |
| 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 a full skill file you can copy and save as
.claude/skills/debugging-guide/SKILL.md in your own Extend Event Handler repository.
View the full debugging skill (SKILL.md)
---
name: debugging-guide
description: >
Expert guide writer and debugging assistant for AccelByte Extend Event Handler apps.
Use when a developer asks for help debugging their event handler, diagnosing startup or
runtime errors, understanding logs, setting up a debugger, or when writing or updating a
debugging guide for an Extend Event Handler 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 Event Handler
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 for an Extend Event Handler 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 Event Handler app shares this event-driven architecture:
\`\`\`
AGS User Action (e.g., login)
│
▼
AGS Kafka Topic
│
▼
Kafka Connect ← bridges Kafka to gRPC
│ gRPC
▼
gRPC Server (port 6565) ← business logic lives here
│
▼
AccelByte AGS Services
\`\`\`
Key environment variables:
| Variable | Purpose |
|---|---|
| `AB_BASE_URL` | AccelByte environment base URL |
| `AB_NAMESPACE` | Game namespace to operate in |
| `AB_CLIENT_ID` / `AB_CLIENT_SECRET` | OAuth client credentials |
| `ITEM_ID_TO_GRANT` | In-game item ID to grant on login events |
| `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.
- Handler is **never called** → Kafka Connect routing or port 6565 reachability.
- Handler returns **Internal** error → business logic, entitlement, or AGS API call.
- AGS API calls **fail** → namespace mismatch, missing permissions, or invalid item ID.
- Debugger **won't pause** → port conflicts or built without debug symbols.
- **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_|ITEM_ID|LOG_LEVEL'`
4. Check ports: `ss -tlnp | grep -E '6565|8080'`
### Step 3 — Common-issue checklist
| Symptom | Likely cause | Where to look |
|---|---|---|
| `ITEM_ID_TO_GRANT environment variable is required` | Missing `ITEM_ID_TO_GRANT` | `main.go` startup check |
| `unable to login using clientId and clientSecret` | Wrong credentials or unreachable `AB_BASE_URL` | `main.go` → OAuth login |
| `Internal` gRPC status on `OnMessage` | Entitlement grant failed | `pkg/service/entitlement.go`, fulfillment API |
| Handler never called for real events | Kafka Connect not routing to local service | Kafka Connect config, ngrok / tunneling |
| Breakpoints never hit | Wrong build mode or port conflict | Use `dlv debug`, check `ss -tlnp \| grep 6565` |
| 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 gRPC server is up
grpcurl -plaintext localhost:6565 list
# Simulate a login event
grpcurl -plaintext \
-d '{"userId":"test-user-001","namespace":"mygame"}' \
localhost:6565 \
accelbyte.iam.account.v1.UserAuthenticationUserLoggedInService/OnMessage
\`\`\`
---
## Write Mode
### Audience
Junior developers and game developers with limited backend experience. Avoid assuming knowledge
of Kafka, gRPC, or protobuf. 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, explanation of each)
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. Triggering events for testing (real AGS events vs. grpcurl simulation)
9. Reading logs (jq filtering, log level table, gRPC call pairs)
10. Common issues (startup failures, handler errors, entitlement failures, Kafka Connect)
11. AI assistance (optional, skip if team doesn't use AI)
### Tone and style
- Use numbered steps for multi-step procedures.
- Use tables for port numbers, environment variables, and symptom/cause/fix mappings.
- Show terminal commands in fenced code blocks with language tags.
- Explain every `grpcurl` example — what it does, what a success looks like, what an error looks like.
- Never assume the reader knows what Kafka, gRPC, or protobuf are.