# How Token Counting Estimates AI Usage and Costs in gh-aw

> Discover how gh-aw uses token counting to estimate AI usage and costs. Learn about the 4-characters-per-token heuristic and precise JSON metadata parsing for accurate financial tracking.

- Repository: [GitHub/gh-aw](https://github.com/github/gh-aw)
- Tags: deep-dive
- Published: 2026-02-16

---

**gh-aw estimates AI usage and costs by applying a 4-characters-per-token heuristic for rapid guardrail checks and parsing JSON usage metadata for precise per-engine token and cost extraction.**

The `github/gh-aw` CLI tool monitors AI-assisted workflow runs by converting raw log output into measurable token counts and monetary costs. Understanding how token counting estimates AI usage and costs helps developers audit spending and prevent runaway token consumption through automated guardrails.

## Two-Stage Token Estimation Pipeline

The implementation uses a dual-layer approach: a fast character-based estimate for safety checks, followed by precise JSON parsing for accurate billing metrics.

### Rough Character-Based Estimation (Guardrail)

For rapid size validation before deep parsing, the `estimateTokens` helper in [`pkg/cli/mcp_logs_guardrail.go`](https://github.com/github/gh-aw/blob/main/pkg/cli/mcp_logs_guardrail.go) applies the industry-standard OpenAI approximation:

```go
const CharsPerToken = 4 // ≈4 characters ≈ 1 token

func estimateTokens(text string) int {
    return len(text) / CharsPerToken
}

```

This logic appears at lines 44-48. When the estimated count exceeds `DefaultMaxMCPLogsOutputTokens` (12,000), the guardrail triggers and returns a truncated JSON payload instead of processing the full log.

### Precise JSON Parsing Per Engine

For accurate accounting, engine-specific parsers extract exact token counts from structured JSON embedded in logs. Each provider reports usage differently, requiring dedicated parsers in the `pkg/workflow/` directory.

**Copilot logs** are processed by [`pkg/workflow/copilot_logs.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/copilot_logs.go), which unmarshals JSON entries and sums `InputTokens` and `OutputTokens` from the `Usage` field:

```go
totalTokenUsage := 0
for _, entry := range jsonEntries {
    totalTokenUsage += entry.Usage.InputTokens + entry.Usage.OutputTokens
}
metrics.TokenUsage = totalTokenUsage

```

**Claude logs** use [`pkg/workflow/claude_logs.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/claude_logs.go), which handles dynamic JSON structures by checking for the `usage` key and extracting `input_tokens` and `output_tokens`:

```go
if usage, ok := jsonData["usage"]; ok {
    if m, ok := usage.(map[string]any); ok {
        in := toInt(m["input_tokens"])
        out := toInt(m["output_tokens"])
        metrics.TokenUsage += in + out
    }
}

```

## Cost Extraction from Provider Logs

After determining token counts, `gh-aw` extracts monetary costs using the generic `ExtractJSONCost` function defined in [`pkg/workflow/metrics.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/metrics.go) at lines 71-88. This helper searches for common cost field names across different AI providers:

```go
func ExtractJSONCost(data map[string]any) float64 {
    // Prefer explicit total_cost_usd
    if v, ok := data["total_cost_usd"]; ok {
        if c := ConvertToFloat(v); c > 0 {
            return c
        }
    }
    // Fallback to common aliases
    costFields := []string{"cost", "price", "amount", "total_cost", "estimated_cost"}
    for _, f := range costFields {
        if v, ok := data[f]; ok {
            if c := ConvertToFloat(v); c > 0 {
                return c
            }
        }
    }
    return 0
}

```

Engine-specific parsers invoke this helper after unmarshaling JSON blocks, adding the result to the running `EstimatedCost` total.

## Aggregation and Guardrail Enforcement

All per-engine metrics converge in the `MetricsData` struct defined in [`pkg/workflow/metrics.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/metrics.go) (lines 29-41):

```go
type MetricsData struct {
    TokenUsage    int
    EstimatedCost float64
    Turns         int
    // …other fields like ToolCalls, Errors
}

```

The guardrail logic in [`pkg/cli/mcp_logs_guardrail.go`](https://github.com/github/gh-aw/blob/main/pkg/cli/mcp_logs_guardrail.go) uses the rough token estimate to protect against oversized responses. When `estimateTokens` returns a value exceeding the configurable `max_tokens` limit (defaulting to 12,000), the system returns a guardrail JSON payload containing the token count and schema information rather than processing the full log content.

## Practical Implementation Examples

### Checking Log Size with the Guardrail

Use the `CheckMCPLogSize` wrapper to validate log size before processing:

```go
import "github.com/github/gh-aw/pkg/cli"

func main() {
    rawLog := fetchMCPLog() // string from MCP server
    trimmed, guarded := cli.CheckMCPLogSize(rawLog, 0) // 0 uses default limit (12,000)
    if guarded {
        fmt.Println("Guardrail triggered:")
        fmt.Println(trimmed) // JSON with token count & schema
        return
    }
    fmt.Println("Log size OK, proceed with normal parsing")
}

```

### Extracting Metrics from Copilot Logs

Parse Copilot-specific logs to extract precise token and cost metrics:

```go
import "github.com/github/gh-aw/pkg/workflow"

func main() {
    // logContent is raw text from Copilot MCP tool
    metrics := workflow.ParseCopilotLog(logContent, true) // true enables verbose parsing
    fmt.Printf("Tokens used: %d\n", metrics.TokenUsage)
    fmt.Printf("Estimated cost: $%.4f\n", metrics.EstimatedCost)
}

```

### CLI Workflow with JSON Output

The complete pipeline from command invocation to metric extraction:

```bash

# Fetch logs and output structured metrics

gh aw logs --workflow my-workflow --run 123456 --json

```

This command executes:

1. **Guardrail validation** using `estimateTokens` against the 12,000 token default
2. **Engine detection** and specific parsing (Copilot, Claude, etc.)
3. **Cost extraction** via `ExtractJSONCost`
4. **JSON serialization** of the `MetricsData` struct containing `token_usage` and `estimated_cost`

## Key Implementation Files

| Purpose | File Path |
|---------|-----------|
| Rough token estimate & guardrail logic | [`pkg/cli/mcp_logs_guardrail.go`](https://github.com/github/gh-aw/blob/main/pkg/cli/mcp_logs_guardrail.go) |
| Generic cost extraction helper | [`pkg/workflow/metrics.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/metrics.go) |
| Copilot log parsing | [`pkg/workflow/copilot_logs.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/copilot_logs.go) |
| Claude log parsing | [`pkg/workflow/claude_logs.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/claude_logs.go) |
| Metrics aggregation struct | [`pkg/workflow/metrics.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/metrics.go) |
| CLI guardrail integration | [`pkg/cli/logs_report.go`](https://github.com/github/gh-aw/blob/main/pkg/cli/logs_report.go) |
| Copilot token extraction tests | [`pkg/workflow/copilot_token_parsing_test.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/copilot_token_parsing_test.go) |
| Claude cost handling tests | [`pkg/workflow/claude_logs_test.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/claude_logs_test.go) |

## Summary

- **gh-aw** employs a two-stage approach to estimate AI usage: a fast character-based heuristic (4 characters per token) for guardrail checks, and precise JSON parsing for accurate billing metrics.
- The **guardrail** in [`pkg/cli/mcp_logs_guardrail.go`](https://github.com/github/gh-aw/blob/main/pkg/cli/mcp_logs_guardrail.go) prevents processing logs exceeding 12,000 tokens by default, protecting against runaway costs.
- **Engine-specific parsers** in [`pkg/workflow/copilot_logs.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/copilot_logs.go) and [`pkg/workflow/claude_logs.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/claude_logs.go) extract exact `input_tokens` and `output_tokens` from structured JSON logs.
- The **cost extraction** helper `ExtractJSONCost` in [`pkg/workflow/metrics.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/metrics.go) normalizes provider-specific cost fields into a unified USD value.
- All metrics aggregate into the **MetricsData** struct, exposing `TokenUsage` and `EstimatedCost` to CLI commands and audit tools.

## Frequently Asked Questions

### How accurate is the 4-characters-per-token estimate?

The 4-characters-per-token heuristic in `estimateTokens` follows the OpenAI rule of thumb for English text. While sufficient for guardrail protection against oversized logs, it typically overestimates actual token counts for code or structured JSON. For billing accuracy, gh-aw relies on provider-reported JSON usage fields rather than this rough estimate.

### What happens when the token limit is exceeded?

When `CheckMCPLogSize` detects that the estimated token count exceeds the configurable `max_tokens` limit (defaulting to 12,000), it immediately returns a guardrail JSON payload containing the token count and schema metadata. The caller receives this truncated response instead of the full log content, preventing excessive API charges and memory consumption.

### Which AI providers does gh-aw support for cost tracking?

Currently, gh-aw implements dedicated parsers for **GitHub Copilot** and **Claude**, located in [`pkg/workflow/copilot_logs.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/copilot_logs.go) and [`pkg/workflow/claude_logs.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/claude_logs.go) respectively. The generic `ExtractJSONCost` helper supports any provider embedding standard cost fields like `total_cost_usd`, `cost`, or `price` in JSON log fragments, making the system extensible to additional engines.

### Can I adjust the token limit for the guardrail?

Yes, the `CheckMCPLogSize` function accepts a `maxTokens` parameter. Passing `0` uses the default limit of 12,000 tokens defined as `DefaultMaxMCPLogsOutputTokens` in [`pkg/cli/mcp_logs_guardrail.go`](https://github.com/github/gh-aw/blob/main/pkg/cli/mcp_logs_guardrail.go). You can specify any positive integer to customize the threshold based on your infrastructure constraints or cost sensitivity.