Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
160660 stories
·
33 followers

Issue 766

1 Share

Comment

G’day y’all!

Pretty monumental week for Apple… Tim cooked, now it’s John Ternus. I remember when Tim first took over, it was an incredibly sad time for all of us with the passing of Jobs, and there was a lot of doubt about how anyone could follow in those footsteps. It did take a while for Cook to find his footing, but once he did, in the latter half of his tenure (IMO), he was off to the races.

Considering Ternus is known as a “hardware” person, I’m really excited to see where he takes the company as the new CEO. With the next event due in less than a week, all eyes will be on Apple to witness the beginning of a new era.

Speaking of App Store, after 16 years building apps for other people, my very first app Yewki has finally launched. If you’re a snowboarder or skier, this one’s for you! It only took 3 weeks, 5 failed submissions, and many, many sleepless coding nights to get here, but I’m proud of the effort I put in and the overall product.

If you’re at NextApp SwiftCon conference next month, I’ll be giving a talk about how I developed it in this new age of AI.

– Andyy Hope

You’ve been learning iOS with Kodeco for 16 years. Now for your entire team.

Kodeco has been helping iOS developers learn since 2010, and as the landscape changes, we’re moving with it—helping you make the most of AI, without losing the craft that you love. Our Apple Foundation Models book shipped in July, with an in-depth course on using AI as a development tool coming this autumn. All of Kodeco’s content is included in Kodeco for Teams, which comes out of a training budget rather than your pocket, and provides access for your whole team. We can show you round in 20 minutes. Or take us to your leader.

News

Phil Schiller reportedly steps down as Apple's App Store and product event chief

Eddy Cue is coming back to the App Store, which I have mixed feelings about, but I also acknowledge the fact that the dynamics of the App Store have changed since his previous tenure, especially in recent years with the influx of new submissions since AI became much more common in app development. I think it will be a net positive to have a (near) fresh set of new eyes on how the operations could be improved.

Code

Why Swift is introducing a warning for weak captures within nested closures

Who doesn’t love a good retain cycle problem and language fix? John details the new retain cycle warning when referencing self within a closure, within a closure.


Effective Geometry: Layout Without `UIScreen.main`

Artem gives a nice breakdown on GeometryReader and how to use it effectively instead of the UIScreen.main we’ve become accustomed to.

Tools

4x Simulator performance with this one weird trick

The folks at MobAI have found a way to drastically slim down the memory footprint of the simulator by culling the unused background services that typically aren’t used in development. I haven’t had a chance to use this yet, but I’d imagine folks with rented or local Mac mini GitHub runners would hugely benefit from this.


Cross-platform Xcode replacement. Build and deploy iOS apps with SwiftPM on Linux, Windows, and macOS.

Another CI-runner-related thing I came across the other week was xtool. The author of this repo claims that this tool enables compiling iOS apps on Windows and Linux systems. I could see some folks utilising a spare PC around the house as a spare CI runner to get some signal on their builds at the very least. I wouldn’t recommend uploading the build artefacts to App Store Connect though.

And finally...

One of the other editors mentioned this in our super secret exclusive Slack chat and thought it might provide a bit of nostalgia to our wonderful readers. It’s a modern port of the After Dark screensavers from the ’90s, which are claimed to be works of art worth preserving. Check out the afterglow app.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

GPT-6 Astra in Microsoft Foundry

1 Share

The next era of AI at work is not going to be about chatting. It is going to be about getting actual work done — and that is exactly the pitch Microsoft made yesterday (September 3) when they announced that GPT-6 Astra, OpenAI’s newest frontier model, started rolling out through the Microsoft Foundry Limited Access Program.

I of course went to look for it in my own Foundry immediately. And… not yet! The reason is that Astra is behind the Limited Access Program, with availability expanding to participating customers over the coming days, so I do not have hands-on time with it to share this time. So this post is my read of what was announced. Hands-on will follow once I get access!

Let’s take a closer look.

What Astra actually is

This is the part that matters. Astra is built to take an open-ended goal, reason through it in multiple steps, build a plan, and produce a finished result. Not a paragraph of advice. A deliverable.

Microsoft groups it into three things:

  • Deliberate planning and decision support – break a challenge into steps, evaluate options, communicate a recommendation, and identify the next actions for review.
  • Polished, purposeful output – apply context, templates and quality standards across a workflow to produce documents, spreadsheets, presentations and analyses that are ready for review.
  • Execution across applications – advanced tool use and computer use, so it can move between apps and complete multi-step tasks with appropriate human oversight.

That third one is the big shift. Astra’s computer-use capabilities are designed to work with familiar applications even where there is no dedicated API — interpreting what is on screen and interacting with approved interfaces. Updating records, navigating development tools, testing software, assembling results into a report.

Think about that for a moment for knowledge work. So much of our day is still glue work between systems that were never designed to talk to each other. This is aimed straight at that. I wrote earlier about computer using Cowork in Copilot Cowork browser use – First Look article, and this continues the story nicely.

However, Astra is much more than just computer using model – as interesting as that aspect is.

Astra is built to take an open-ended challenge, reason through it in multiple steps, create a plan, and produce a polished result. It can weigh trade-offs, incorporate new direction as work progresses, and use tools across applications and systems. 

The specs – 1.05M context and new reasoning levels

From OpenAI’s own model page, the numbers are:

  • 1,050,000-token context window
  • 128,000 max output tokens
  • Knowledge cutoff: April 30, 2026
  • Input: text and image. Output: text.
  • Reasoning effort now goes beyond high with two new levels: xhigh and max
  • Tool support covers computer use, hosted shell, apply patch, skills, MCP and tool search. Fine-tuning is not supported.

A million-token window is not just a bigger bucket. It means a whole project — the specs, the meeting notes, the backlog, the previous drafts — can live in one working session without constantly being squeezed into a summary. For long agentic runs, that is the difference between an agent that remembers why something failed and one that quietly forgets.

Being able to dial reasoning from low all the way up to max is also very practical. Not every task deserves maximum compute, and now that is a per-call decision.

Pricing

Microsoft published Astra pricing for Standard deployments, including Global and U.S. Data Zone. It is consumption-based, so teams can start building without committing to reserved capacity.

DeploymentInputCached inputCached writesOutput
Standard Global (Short context)$10.00$1.00$12.50$50.00
Standard Global (Long context)$20.00$2.00$25.00$75.00
Standard Data Zone (US) (Short context)$11.00$1.10$13.75$55.00
Standard Data Zone (US) (Long context)$22.00$2.20$27.50$82.50

(USD per million tokens, per Microsoft.)

So long context is genuinely a premium tier — roughly double on input. That is something to design for, not stumble into. Microsoft notes Astra is designed for token efficiency on complex work, and that actual usage and costs vary by workload and configuration. Which is the honest answer: your bill depends on how you build.

And a note for us here in Europe — the published deployment options are Global and U.S. Data Zone.

Enterprise controls – because this capability demands containment

I really like that Microsoft did not gloss over this part. When a model can operate software on your behalf, the risk surface changes completely. Content in an application may be incomplete, misleading, or deliberately crafted to influence an agent’s behavior. That is prompt injection with hands.

Foundry’s answer is the fundamentals: Microsoft Entra identity and access management, encryption in transit and at rest, private networking options, role-based access controls, content filtering, safety evaluations, monitoring and governance tools. Microsoft also states plainly that prompts and outputs are not used to train the models.

The design guidance is the bit I would print out and stick on the wall: scoped credentials, approved resources, human checkpoints for consequential actions, and activity records aligned to your risk requirements. And Microsoft is refreshingly clear that none of this eliminates risk or replaces your own responsibility to select and configure the right controls.

OpenAI describes Astra as its most aligned model to date and plans to publish alignment, safety and computer-use evaluations in its launch materials.

Where I would start

Microsoft’s own list of scenarios reads like a Future Work to-do list:

  • Software engineering – reproduce complex bugs, investigate likely causes, propose fixes, prepare changes for developer review
  • Business intelligence – build and refine dashboards in Power BI, compare data and prepare insights to share
  • Professional work – documents, spreadsheets and presentations that follow existing templates and business standards
  • Application workflows – updating customer records, processing forms, testing websites, working through approved interfaces where APIs are limited

When access opens up: explore the model in Foundry Models, then build with the Foundry Agent Service to bring cross-application task execution into your workflows.

So what does this mean?

I am very curious about this one. Not because of a benchmark number, but because the shape of the product changed. A million tokens of context, plus real computer use, plus enterprise-grade governance in Foundry — that is the combination that finally makes “an agent completes a unit of work” something you can actually put in front of a compliance team.

The honest caveats: it is a Limited Access Program rollout, so most of us are waiting. The long-context tier means the temptation to throw everything into that million-token window comes with a real bill attached. And the deployment regions are Global and US for now.

Work is changing. This one moves it.

Read Microsoft GPT-6 Astra in Foundry announcement.

Have you already got GPT-6 Astra in your Foundry? I would like to hear what you are building with it!





Read the whole story
alvinashcraft
17 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Announcing Project Zenith: The ready-to-code Windows experience out of the box on developer-class devices

1 Share
Breakthroughs in computing become real through the experiences developers create. Our goal is to have Windows be the best platform for developers. That means maximizing the latest in hardware innovation with software optimized for the modern builder. At Build 2026, we introduced a developer-optimized Windows 11 experience to build and ship faster and shared how Windows is becoming the open platform for secure agentic experiences. Today, we're taking the next step with Project Zenith, a ready-to-code distraction-free Windows experience on developer-class devices with 64 GB+ unified memory and 250+ GB/s memory bandwidth, so developers can jump right in. Project Zenith will first become available with AMD’s Ryzen AI Halo, with more devices from our OEM and silicon partners available in the coming months. Project Zenith devices come with a preconfigured Windows setup for development and a set of tools curated for what developers reach for first.  On these devices, developers can run 30B+ parameter models locally and unmetered — accelerating experimentation while helping reduce reliance on metered cloud tokens. Project Zenith represents Windows at its best by listening to developers, responding with purpose and working across our ecosystem to raise the bar for what a developer PC can be.

Improving the baseline Windows 11 experience

Since the beginning of this year, we’ve been dedicated to making Windows 11 more performant, reliable and refined across day-to-day experiences. From Search to File Explorer, and everyday memory usage, Windows is becoming faster, calmer, and more polished with more control over the experiences you care about most. Project Zenith devices will benefit from all these improvements, while delivering an optimized developer environment you’ve been asking for.

Ready to code out of the box

Project Zenith comes with a set of pre-installed tools spanning languages and runtimes, source control, and productivity tools. Windows Terminal and Visual Studio Code are pinned to the Taskbar by default, putting your favorite tools within immediate reach. Chart showing pre-installed tools We’ve also pre-configured Windows Settings for coding across File Explorer, Search, Start, and the Taskbar. File Explorer shows file extensions, hidden files, the full path in the title bar, and the details pane, with long-path support enabled. Recently used files and folders and sync provider tips are turned off for a cleaner workspace. In Search and Start, Command Palette is enabled, while Start menu tips and account notifications are turned off to reduce distractions. Windows Subsystem for Linux (WSL) has become foundational for running Linux workloads on Windows. Last year we open-sourced WSL. Building on that momentum at Build 2026, we integrated WSL more deeply into Windows with WSL containers to provide a built-in way to create, run, and interact with Linux containers directly on Windows. Together, with Project Zenith, these choices create a calm, and meaningful starting point for development. Project Zenith is not about prescribing a single workflow. You can continue to configure, extend, and personalize your environment around the tools, languages, and frameworks you prefer. It is about beginning from a better baseline, shaped by the settings and tools developers reach for first, so a new device works with you to accelerate your workflows.

Secure platform for agents on developer-class hardware

As agentic applications become more common, developers need a secure foundation for building and running them. At Build 2026, we shared how Windows is evolving to provide OS-enforced identity, containment with Microsoft Execution Containers (MXC), and enterprise-grade manageability for agents.  Project Zenith devices will benefit from these platform investments from day one, providing a secure environment for developing and running agents. AI models are becoming more powerful with agentic coding workflows demanding continuous compute. By shifting some of that intelligence to the edge, we are achieving token economics and transforming the developer experience: frontier models tackle frontier problems, while everything else runs locally at scale. With 64 GB+ of unified memory and 250 GB/s+ of memory bandwidth, Project Zenith devices let developers run capable coding models locally and unmetered, helping lower token costs.

Project Zenith is just the beginning

Over the past year, we have continued to evolve Windows around how development happens today. From WSL and Windows Developer Configurations to Intelligent Terminal, Coreutils for Windows and expanded on-device AI capabilities, each investment is focused on helping developers stay in control and build faster.  Project Zenith brings together the work happening across the Windows platform into a tailored experience on new developer-class PCs. Project Zenith also reflects how Windows moves forward with our ecosystem. By working with our OEM partners, we give developers choice across devices and performance tiers while preserving the ready-to-code experience. The hardware may vary, but the developer promise is consistent: powerful for modern development, thoughtfully configured and ready for developers from the start. Project Zenith is the result of listening to developers about what they need from Windows today, and where they want the platform to go next. As AI continues to transform software development, we are committed to evolving Windows alongside the community that builds on it. We are excited to see what you will build, making Windows the platform where the next generation of applications, agents, and AI experiences come to life.
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Claude Code Observability with OpenTelemetry

1 Share

Agentic coding tools like Claude Code, OpenAI Codex, Google Antigravity, and Cursor have become ubiquitous for everyday software development.

As agentic systems mature, much of the work developers have them do is delegated, one subagent at a time. Many teams are also exploring and using a shared, multi-tenant Agentic Infrastructure, where cost isn't tied to a single owner. That's where Observability becomes key to monitoring infrastructure costs.

In this guide, you'll learn how observability works, then enable Claude Code's built-in telemetry, run a backend to collect it, and read the metrics, logs, and traces it emits. This will help you start tracking your team's costs more effectively, and it'll only improve as emitted telemetry matures and correlates more cleanly with your sessions.

Note: In its current state, the emitted telemetry from Claude Code provides no attributes that allow a reliable map to named sessions. Usage can be tracked using session_id, but it's still clumsy in a longer session mixing multiple prompts/skills.

This guide is scoped to Claude Code's telemetry for metrics, logs, and tracing. Note that it applies to Linux and macOS only.

Table of Contents

Observability with OpenTelemetry

Observability is the ability to answer questions about a system's runtime behavior from the data it emits. You do this without looking into its internals, attaching a debugger, reading source code, or manually trying to reproduce the behavior.

Here, a system's runtime behavior means what's externally visible. You can ask questions like:

  • How much time 95% of all requests take.

  • What the failure rate is across all requests received.

  • What the cache hit ratio is for the in-memory cache the service uses.

  • The difference between the configured and deployed replica counts for a service.

For Claude Code, the inaccessible inner workings are: how it manages context, how work is divided across multiple LLM calls, and how subagents are orchestrated. But you can read the emitted telemetry from Claude code to answer questions like:

  • How much a dev or a team spent over a day, week, or month.

  • How that usage is distributed across the supported models and effort levels.

  • How many tokens are spent per dollar, and how much that varies by type (input, output, cacheRead, cacheCreation).

  • When a compaction event kicked in, and by how much it reduced the context's token usage.

Only an instrumented system can answer these questions. Instrumentation is a piece of code added by the developer or built into the tool that records a program's runtime behavior and emits it as telemetry. For example, a measurement like this request spent 100 tokens.

The telemetry data helps avoid silent failures by providing a well-structured data trail of the system's behavior over time. For example, here's a chart from GitHub's August 17, 2026 outage postmortem explaining a rise in GitHub Actions runs over time from ~30M to ~110M:

Github Actions Growth

Telemetry Data

The emitted telemetry consists of three categories of data:

  • Metrics: Numeric measurements aggregated over a time window, like queries per second (QPS).

  • Logs: A detailed record of an individual event, with a timestamp. For example, a compaction event in Claude Code.

  • Traces: The path of one request through the system, split by time into nested requests. For example, an order-placement request on an ecommerce website, showing which internal services it calls to complete the request.

OpenTelemetry (https://opentelemetry.io/) is an observability framework that helps you generate, collect, and export these signals to a backend, which handles storage, querying, and visualization. Keeping that split makes it tool/vendor agnostic where the backend can be open-source or proprietary. It provides instrumentation SDKs for multiple programming languages.

Instrumenting Claude Code

Instrumentation code usually runs alongside the application, at the points best for measuring: a middleware with a request arriving or a response going out, or a token being counted.

It is plugged into the application in two ways:

  1. A Shared Instrumentation Library: Applications using an open source framework can add an instrumentation library as a dependency and link it with the application's lifecycle methods. OpenTelemetry publishes instrumentation libraries for many frameworks (Example: Spring Framework).

  2. Customized Implementation by Application Developers: Telemetry data emitters are added directly in the codebase using the OpenTelemetry SDK. For a closed-source product, the code is private, but it can still emit telemetry data compatible with OpenTelemetry standards.

Example: HTTP Instrumentation

Middleware in HTTP handling is a common codepath for all requests, which is why it's chosen for application-wide settings like authentication. The same reason makes it a good place for instrumentation code: wrap the handler once, measure every request.

HTTP Instrumentation example

The diagram shows where instrumentation sits relative to the request path.

  • A client request passes through an HTTP middleware before reaching the application's request handler and downstream calls. The middleware uses SDK constructs to time the handler and record duration, status, and a count.

  • The SDK then buffers those measurements and pushes OTLP to the Collector on a background thread, off the request path.

Claude Code is the second case, where both the core app and its instrumentation module are provided by Anthropic. The code that measures token usage, cost, and tool calls is built in and emits OpenTelemetry over OTLP. As a Claude Code user, you only need to enable the telemetry and prepare a backend to receive and analyze it.

Note: OTLP is a telemetry data delivery protocol designed in the scope of the OpenTelemetry project. This guide assumes end-to-end compatibility with OTLP. Wiring up incompatible telemetry or using incompatible backend components can give unpredictable results and is out of scope.

To collect, store, and read the telemetry, you'll need the following components:

  • OpenTelemetry Collector: A vendor-agnostic implementation of how to receive, process, and export telemetry data. This is optional but great to have for personal setup. Must have for a production use case.

  • Jaeger: Distributed tracing backend, released as an open source tool by Uber.

  • Prometheus: Collects and stores metrics as Timeseries Data.

  • Loki: Scalable Log Aggregation system by Grafana.

  • Grafana: for UI visualization of logs, metrics, and traces.

Pull vs Push: How Telemetry Leaves an App

Telemetry leaves an application in one of two ways:

Pull (Scrape): The app exposes its current metrics on an HTTP endpoint, and a scraper (Prometheus) reads that endpoint periodically. Every running instance needs its own port, and the scraper must know all of those addresses ahead of time. The app is passive: the scraper drives the data movement. This suits long-lived processes with stable addresses.

In OpenTelemetry, pull-based scraping is configured with OTEL_METRICS_EXPORTER=prometheus (see the SDK environment variables for the accepted exporter values).

By convention, the app makes its metrics available at http://localhost:9464/metrics. This exporter handles metrics only.

Push (OTLP): The app sends its telemetry to a receiving endpoint on a regular interval. This is more flexible: any number of processes can push to the same endpoint with no registration ahead of time, so apps can start and stop freely even as their addresses change.

In OpenTelemetry, push is configured with OTEL_METRICS_EXPORTER=otlp, which ships over the OTLP exporter.

The push model can carry metrics, logs, and traces.

When to Run a Collector

A Collector is deployed when the existing stack isn't enough to handle the system's growing scale and complexity. It helps in the following ways:

  • One export config: every producer points at the Collector instead of each carrying its own per-backend exporter setup.

  • Outbound-only connections: enterprise networks often block the inbound connections a pull-based scraper needs. With a Collector, the app pushes out to it and it pushes onward, so nothing has to accept inbound traffic.

  • Fan-out and translation: the Collector can convert telemetry into a vendor's storage format and send the same signal to more than one backend.

  • Buffering: if a backend goes down, the Collector holds the data and retries, absorbing transient failures.

  • Processing: it can apply processors before data leaves for storage, such as redacting attributes.

Note: This guide runs a push-based configuration with a Collector even though the setup is single-user. The stack has three backends that store and query data differently, and letting the Collector receive Claude Code's OTLP once and route each signal to the right place is simpler than wiring the app to all three. That's why the tool list calls it optional for personal use but a must-have in production: its value grows with the number of producers and backends.

Prerequisites

Each section in this guide links relevant docs, but you'll make faster progress if the tools and query languages specified below are already familiar:

You'll need

  • Latest version of Claude Code

  • Docker Engine (29.4.1+) with Docker Compose.

    • Capacity to run 5 Containers (4-core CPU, 8GB RAM, 15GB Disk)
  • A Claude Plan that includes Enhanced Telemetry beta (Pro+ / Max)

  • Git

  • Ensure these ports are free at localhost:

    • 3000 (Grafana)

    • 3100 (Loki)

    • 4317/4318 (OTel Collector OTLP gRPC/HTTP)

    • 9090 (Prometheus)

    • 16686 (Jaeger UI)

Knowledge that would help

Setup

The test observability stack is deployed using Docker Compose. For telemetry export to work, Claude Code must be able to reach Collector's OTLP endpoint which is localhost:4317 (gRPC) or localhost:4318(HTTP) when running on same machine. All backend services run in a container with their own Docker volume for persistence.

Instrumentation Setup

The diagram shows how the telemetry components we're using are linked.

  • Apart from Claude Code, every component runs in a container managed by Docker Compose.

  • Multiple instances of Claude Code running on any machine (host or cloud VM) should be able to export telemetry to the Collector as long as those machines have connectivity to it (ports 4317/4318 of the host running the Collector container are reachable).

Moving from top to bottom:

  • Claude Code exports all three signals over OTLP to the Collector.

  • The Collector then splits them by type, pushing traces to Jaeger and logs to Loki, while exposing metrics on port 8889 for Prometheus to scrape.

  • Jaeger, Prometheus, and Loki each persist to their own Docker volume.

  • Grafana queries all three as the single dashboard layer.

The goal here is a live stream of telemetry from Claude Code that gets stored in a backend and can be queried on demand, during a session or long after. Two things must be in place:

  • Enable telemetry in Claude Code. The instrumentation is built in but emits nothing until telemetry is enabled and its OTLP exporter points at the Collector.

  • Run the observability backend. The Collector processes each signal, forwarding it to Prometheus, Loki, and Jaeger for storing and serving queries.

You'll start the backend first, so the telemetry has somewhere to go.

Start the Observability Backend

Before enabling telemetry in Claude Code, ensure the stack is up to collect, process, and read the data. The code for the test observability backend lives in this Github repo.

The repo has the following structure:

.
├── README.md
└── compose
    ├── docker-compose.yml # Docker config for 5 containers in the observability stack. Applies pinned image versions, port mappings and named volumes for each service.
    ├── grafana
    │   └── provisioning
    │       ├── alerting
    │       ├── dashboards
    │       ├── datasources # datasources(Prometheus, Loki, Jaeger) and dashboards. Empty initially.
    │       └── plugins
    ├── jaeger-config.yaml # Jaeger v2, badger (local-file) storage for traces. Ties to the user: root TIP below.
    ├── loki-config.yaml # single-binary Loki, filesystem storage. Near default settings.
    ├── otel-collector-config.yaml # receive/process/export pipeline: OTLP in on 4317/4318, traces out to Jaeger, logs to Loki, metrics exposed on :8889 for Prometheus.
    └── prometheus.yml # a single scrape job against the Collector's :8889, 30s interval.

You'll only need docker compose command to start the containers. It reads docker-compose.yml and starts the containers, linking them to their respective config files.

Connectivity between containers:

All containers start within the same Docker network, which allows them to communicate using container names directly. For example, collector's exporter config uses container names:

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889
  otlphttp/loki:
    endpoint: http://loki:3100/otlp

Note that it doesn't contain Prometheus config, since Prometheus ends up scraping it from the collector as configured in prometheus.yml:

global:
  scrape_interval: 30s

scrape_configs:
  - job_name: otel-collector
    static_configs:
      - targets: ["otel-collector:8889"]

Start the containers using Docker compose:

git clone https://github.com/ps-mir/otel-dev-stack.git
cd otel-dev-stack/compose
docker compose up -d

# Output
 ✔ Volume compose_loki_data                          Created                                                                                                                                          0.0s
 ✔ Volume compose_grafana_data                       Created                                                                                                                                          0.0s
 ✔ Volume compose_prometheus_data                    Created                                                                                                                                          0.0s
 ✔ Volume compose_jaeger_data                        Created                                                                                                                                          0.0s
 ✔ Network compose_default                           Created                                                                                                                                          0.1s
 ✔ Container compose-prometheus-1                    Started                                                                                                                                          4.1s
 ✔ Container compose-loki-1                          Started                                                                                                                                          4.2s
 ✔ Container compose-jaeger-1                        Started                                                                                                                                          4.3s
 ✔ Container compose-otel-collector-1                Started                                                                                                                                          3.3s
 ✔ Container compose-grafana-1                       Started                                                                                                                                          2.7s

Check container status:

# all five services should show "Up"
docker compose ps

# Output
NAME                       IMAGE                                              COMMAND                  SERVICE          CREATED         STATUS         PORTS
compose-grafana-1          grafana/grafana:13.2.0                            "/run.sh"                grafana          3 minutes ago   Up 3 minutes   0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp
compose-jaeger-1           cr.jaegertracing.io/jaegertracing/jaeger:2.20.0   "/go/bin/jaeger --co…"   jaeger           3 minutes ago   Up 3 minutes   0.0.0.0:16686->16686/tcp, [::]:16686->16686/tcp
compose-loki-1             grafana/loki:3.7.6                                "/usr/bin/loki -conf…"   loki             3 minutes ago   Up 3 minutes   0.0.0.0:3100->3100/tcp, [::]:3100->3100/tcp
compose-otel-collector-1   otel/opentelemetry-collector-contrib:0.159.0      "/otelcol-contrib --…"   otel-collector   3 minutes ago   Up 3 minutes   0.0.0.0:4317-4318->4317-4318/tcp, [::]:4317-4318->4317-4318/tcp, 55679/tcp
compose-prometheus-1       prom/prometheus:v3.11.2                           "/bin/prometheus --c…"   prometheus       3 minutes ago   Up 3 minutes   0.0.0.0:9090->9090/tcp, [::]:9090->9090/tcp

TIP: Jaeger runs as user: root (Compose file) to create the badger dir. Not doing so causes a failure: mkdir /badger/key: permission denied. Jaeger itself doesn't need root permission, but Docker volumes are owned by root:root on first mount.

Enable Telemetry

OpenTelemetry instrumentation, once added to an application, stays disabled until specific configuration enables it.

There are two ways to enable telemetry in Claude Code:

1. Environment Variables

Setting specific environment variables enables telemetry generation. Beyond the standard OTEL_* variables, Claude Code defines its own CLAUDE_CODE_* variables.

This guide uses the following settings:

# master switch: when unset or 0, Claude Code produces no telemetry at all
export CLAUDE_CODE_ENABLE_TELEMETRY=1

# opt into the beta enhanced-telemetry attributes and events (extra session and tool detail)
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1

# per-signal exporter selection; "otlp" ships the signal over OTLP.
# other accepted values are "console" (print locally), "prometheus" (metrics only), and "none" (drop the signal)
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_TRACES_EXPORTER=otlp

# OTLP transport: "grpc" talks to the collector's 4317 port; "http/protobuf" would use 4318
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc

# one endpoint for all three signals: the collector's OTLP listener on the local machine
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317

# how often metrics are flushed, in milliseconds; the default is 60000 (60s),
# shortened here so a manual check sees fresh data without a long wait
export OTEL_METRIC_EXPORT_INTERVAL=5000

# emit cumulative counters instead of delta (see "Aggregation Temporality" below)
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative

Environment variables, however, are process-wide and can affect more than Claude Code. For example,

  • Accidentally enabling instrumentation in other applications.

  • Interfering with OpenTelemetry code/tests if you're developing your own instrumentation or working on any OpenTelemetry SDK.

2. Claude Code settings.json

OpenTelemetry defines Declarative Config, a YAML based configuration to enable telemetry, but Claude Code doesn't support it. But it lets you set the same env variables in ~/.claude/settings.json. That isn't declarative config, but it's better than shell environment variables because it applies only to Claude Code. An example:

{
  "effortLevel": "medium",
  "tui": "fullscreen",
  "env": {
     "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
     "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
     "OTEL_METRICS_EXPORTER": "otlp",
     "OTEL_LOGS_EXPORTER": "otlp",
     "OTEL_TRACES_EXPORTER": "otlp",
     "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
     "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317",
     "OTEL_METRIC_EXPORT_INTERVAL": "5000",
     "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative"
  }
}

Only the env block matters for telemetry. effortLevel and tui are unrelated settings you may already have. The variables match the annotated list above.

Aggregation Temporality

Prometheus counter type metrics only increase over time. Their raw value isn't useful, so you read them through per-second growth (rate()) or total growth over a time window (increase()).

Aggregation temporality decides what number a counter reports on each telemetry export: the change since the previous export(Delta), or the running total since the process started(Cumulative).

A short example. Say Claude Code spends tokens over four 5-second export intervals:

Export at Tokens since last export Delta value sent Cumulative value sent
0s (start) -- -- 0
5s 100 100 100
10s 0 0 100
15s 250 250 350
20s 50 50 400

By default, Claude Code emits metrics with AggregationTemporality: Delta. This can be inspected and confirmed from the Collector's container logs using the command:

# Command only works from directory containing docker-compose.yml
docker compose logs otel-collector

Note: To enable detailed logs in the Collector, you need to add the debug exporter to the Collector config.

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger, debug]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus, debug]

Then restart the container:

# Command only works from directory containing docker-compose.yml
docker compose up -d --force-recreate otel-collector

Log output with AggregationTemporality: Delta:

otel-collector-1  | Descriptor:
otel-collector-1  |      -> Name: claude_code.active_time.total
otel-collector-1  |      -> Description: Total active time in seconds
otel-collector-1  |      -> Unit: s
otel-collector-1  |      -> DataType: Sum
otel-collector-1  |      -> IsMonotonic: true
otel-collector-1  |      -> AggregationTemporality: Delta <---
otel-collector-1  | NumberDataPoints #0

Delta doesn't work well with Prometheus functions like rate()/increase(), since they expect cumulative values.

Setting OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative switches the exported metrics from delta to cumulative temporality. It has been added to both the env and JSON config used in this guide.

As before, you need to restart the Collector container after any config change for it to take effect.

Exploring Telemetry

With telemetry flowing, you can start querying it. Metrics, logs, and traces each answer a different kind of question about Claude Code usage, so the three sections below are largely independent.

The data behind them comes from two places.

  • The Metrics and Logs sections query whatever Claude Code usage has accumulated in the backend, so your panels will show your own sessions and the numbers won't match the screenshots. Give it a few real sessions before expecting much to show.

  • The Tracing section instead walks a single deliberate run, a custom skill summarizing a batch of meetings, described in enough detail to follow along. You don't need to reproduce it.

Metrics

Metrics are the aggregate, time-windowed view of Claude Code usage. Example: total cost, token volume, and how each trends and breaks down by attributes like model, effort, and token type. Use them to watch spend and spot shifts in consumption.

Each metric is a numeric measurement recorded over time, a time series of timestamped values you can plot or aggregate. Claude Code's metrics are running totals (counters), so a query reports the change over a chosen window rather than the raw value. See Aggregation Temporality above for how that works.

Prometheus is the metrics backend we're using here. It scrapes the Collector, stores the series, and answers queries written in PromQL. Grafana reads the same data for dashboards at localhost:3000. The full list of metrics and their attributes is in the Claude Code monitoring docs.

Open Prometheus in your browser (localhost:9090), type claude in the query field, and you should see the supported metrics:

Available Claude Code metrics of counter type in Prometheus.

For each metric below, you'll explore it first using PromQL, and then use the same query to add it to the Grafana dashboard as a panel.

Total USD Spent

claude_code_cost_usage_USD_total represents cumulative usage cost, in USD, tracked per session. It's useful for controlling budgets and spotting sudden spikes in usage.

This is a client-side estimate based on token counts priced at Anthropic's per-model, per-type rates and accumulated. It's completely normal for it to exceed your Claude Code plan's subscription cost.

Note: This metric is more critical if you're paying per raw API call. A subscription gives you a usage allowance with increased but bounded rate limits.

Test the following query in Prometheus first (localhost:9090/query):

sum(increase(claude_code_cost_usage_USD_total[10m]))

increase(...[10m]) gives the counter's growth over the last 10 minutes. sum(...) with no by clause collapses the per-attribute series (model, effort, and others) into one number.

For a Grafana panel, swap the fixed [10m] window for the $__range built-in variable so the value follows the dashboard's time picker:

sum(increase(claude_code_cost_usage_USD_total[$__range]))

To add it as a panel, open Explore, select Prometheus as the data source, and run the query. The result depends on how much you've used Claude Code in the window.

Grafana USD Total Stat - Metrics explorer view of the query.

On adding to the dashboard you should get more Panel Options. Select the Stat panel:

Total USD Stat Panel

This panel needs to be added to the Grafana dashboard.

Total Token Usage

claude_code_token_usage_tokens_total is the cumulative token count, with the same counter shape as the USD cost metric. Read a raw series in Prometheus first to see which labels you can aggregate by. A single series looks like:

claude_code_token_usage_tokens_total{effort="high", exported_job="claude-code", instance="otel-collector:8889", job="otel-collector", model="claude-sonnet-5", otel_scope_name="com.anthropic.claude_code", otel_scope_version="2.1.252", query_source="auxiliary", session_id="e0b9795b-4da3-4171-8fa6-a2866bf44d86", terminal_type="ssh-session", type="cacheCreation"}    213730

The trailing number is the counter value. Key attributes you'll be working with are type, model, and effort. The Token Usage by Type chart below groups on type.

The window total is the same query shape as Total USD Spent, with the token counter:

sum(increase(claude_code_token_usage_tokens_total[$__range]))

Tokens Per USD

Unlike the previous two metrics, this is a derived figure, calculated over a time window as Total Tokens / Total Cost.

sum(increase(claude_code_token_usage_tokens_total[$__range])) / sum(increase(claude_code_cost_usage_USD_total[$__range]))

This one number collapses every attribute combination into a single value. Each distinct combination, for example model A at medium effort versus model B at high effort, is its own time series, and the query sums across all of them.

To analyze a specific combination, run the same ratio split by an attribute and compare.

sum by (model) (increase(claude_code_token_usage_tokens_total[$__range]))
  / sum by (model) (increase(claude_code_cost_usage_USD_total[$__range]))

Swap model for effort or type; the raw series above lists the rest of the labels.

Overall Result:

Stat Panel Aggregated Metrics

Stats Panel(6hr window): Total USD Spent ($4.28), Total Tokens Spent (3.02M), and Tokens Per USD (707k).

Token Usage by Type

You'll see how claude_code_token_usage_tokens_total changes over time, broken down by type. A single stat hides the shape, so use a time-series panel.

The type attribute has four values, which differ a lot in cost:

  • cacheRead: tokens served from an existing cache entry. They dominate token spend in a long session, and are cheaper than the baseline rate.

  • cacheCreation: tokens written into the prompt cache on the first prefix load. Costly.

  • input: new, uncached prompt tokens.

  • output: model-generated tokens.

To also see the total, use two queries: the per-type breakdown and the un-split total for reference.

# per-type breakdown
sum by (type) (increase(claude_code_token_usage_tokens_total[$__rate_interval]))
# total
sum(increase(claude_code_token_usage_tokens_total[$__rate_interval]))

__rate_interval is Grafana's per-step window for time-series panels, the counterpart to the __range used for the stat panels above.

Graphana Explore Timeseries

The two queries running in Explore, before saving them as a panel.

After adding to the dashboard:

Running token usage by type

Each spike is a burst of Claude Code activity, the flat stretches are idle time. Hovering a point splits the total into the four types: here the total is about 1.0M tokens, of which cacheRead is about 925k (roughly 92%), the rest cacheCreation, output, and input.

TIP: Token consumption is dominated by cacheRead, which is also the cheapest type.

Token Usage by Model and Effort

This is the concrete version of the breakdown suggested under Tokens Per USD: which model and effort pairs are actually consuming tokens.

sum by (model, effort) (
  increase(claude_code_token_usage_tokens_total[$__rate_interval])
)
Running token usage by model and effort

Here the token counter is grouped by its model and effort attributes. In this window every series is claude-sonnet-5 at either medium or high effort, and one burst of medium effort near 18:13 reaches about 2.6M tokens. The number of unique groupings depends on the cardinality of the chosen attributes.

Logs

A log record is a timestamped event with its full field set attached. You query logs when you want that specific event and the context around it: what happened, when, and with which values.

Metrics are the pre-aggregated form of the same activity. Anything that means counting, summing, or taking percentiles across many records belongs in a metric. If you're aggregating log output downstream, that data should have been a metric from the start.

Logs are the right tool for:

  1. Per-event context: the full detail of one occurrence, not a rolled-up number.

  2. Discrete or irregular events: a compaction firing, a session start, or an API error.

  3. Post-incident forensics: reading raw records back while debugging after the fact.

  4. Trace correlation: a log line carrying a trace and span ID drops you into the request it came from.

The log backend we're using here is Loki, queried with LogQL. Running logs through a backend like this buys you:

  1. Structured fields: filter and compute on named keys instead of regex over text.

  2. Field indexing: label lookups return without scanning every line.

  3. Trace and span correlation: pivot from a log to its trace, or pull every log for one trace.

  4. Time-bounded queries: each query is scoped to a window, keeping the scan cheap.

Grafana reads Loki for dashboards, the same as it does for Prometheus.

Compaction Event

Compaction is Claude Code trimming its own context when it grows too large. Each compaction emits a log event (event_name="compaction") carrying the token counts before and after (pre_tokens, post_tokens) and the span_id it happened under, so a query over those events shows how often it fires and how much it reclaims each time.

In Grafana Explore, select Loki as the data source and paste the LogQL below. It selects the compaction events, parses their fields with logfmt, and derives a per-event reduction percentage with label_format. The fields only exist once compaction has actually happened, so trigger a few first.

{service_name="claude-code"} | event_name="compaction"
  | logfmt
  | label_format reduction_pct=`{{ printf "%.1f" (mulf (divf (subf .pre_tokens .post_tokens) .pre_tokens) 100) }}`

Deriving reduction_pct for each record is fine here because it stays per-event. A running average across compactions would belong in a metric.

 LogQL query for compaction event against Loki data source.

The label_format line adds a reduction_pct label. To show it as a table, switch the panel to Table view and add three Grafana transformations:

  1. Extract fields from the labels object.

  2. Filter fields by name to keep Time, pre_tokens, post_tokens, reduction_pct, and span_id.

  3. Convert field type to turn pre_tokens, post_tokens, and reduction_pct into numbers.

Compaction events with pre/post token counts and the derived reduction percentage.

Tracing

Tracing provides a detailed picture of the full path a request takes through an application, from start to completion. Some fundamental concepts behind tracing:

  • Span: a timed operation representing a unit of work. Building block for traces. All trace data is recorded as a sequence of spans, and each has a type, its operation name:

    • claude_code.interaction: one prompt and everything Claude Code does to answer it. Normally the root span, so one interaction is effectively one trace.

    • claude_code.llm_request: a single model call inside an interaction.

    • claude_code.tool: a single tool call inside an interaction (Bash, Write, Agent, and so on).

  • Trace: a tree of spans representing a request path from start to completion.

  • Session: one Claude Code run, identified by session.id. It can result in many interactions and traces.

  • Subagent: a nested Claude Code instance started by the Agent tool, running its own interactions.

Jaeger is the tracing backend used here. The Collector forwards spans to it over OTLP. Jaeger stores them and lets you search traces by service and span tags and inspect each one as a span tree. Everything below uses its UI at localhost:16686.

Generating Traces

To generate trace data, this guide will use a test prompt to spawn agents and prepare some text. This prompt was tested with Sonnet 5 at medium effort.

You can paste the prompt directly into Claude Code:

Spawn 4 subagents in parallel, one per topic below. Each subagent researches its topic from your own knowledge and returns a ~150-word summary with 3 key points. Do not have them read files or run commands.
Topics:
1. How TCP congestion control works
2. The CAP theorem
3. How DNS resolution works
4. What a Bloom filter is

Once all 4 return, combine the summaries into one markdown document and write it to summary.md
Snapshot when running the prompt.

Note: Ask Claude Code for the session_id in the same session after the prompt finishes. This will be used to find related traces in Jaeger.

Trace By Session ID

Finding traces by session_id.

The search filters by service = claude-code and the tag session.id=<id>. It returns 6 traces, all rooted at claude_code.interaction, with span counts from 1 to 20 and durations from about 1 second to 33 seconds.

The list alone doesn't say which trace did what. Going through them by hand, or scripting it against the trace API for a real session, gives the following:

# Trace Name Spans Duration llm_calls tools
1 claude_code.interaction 1 1.4s 0
2 claude_code.interaction 3 4.6s 2
3 claude_code.interaction 1 5.4s 0
4 claude_code.interaction 1 2.5s 0
5 claude_code.interaction 20 15.7s 7 Agent(x4)
6 claude_code.interaction 15 32.5s 5 ScheduleWakeup(x2), Write(x1)

A few observations:

  • Half the traces are noise. Traces 1, 3, and 4 are single-span interactions with no model call or tool, an idle session being pinged. Trace 2 is a brief exchange. Only Traces 5 and 6 are the run.

  • The parallel dispatch is a single interaction. Trace 5 fires all four Agent calls inside one claude_code.interaction. Their nested model calls (7 to 10 seconds each) overlap, so the interaction finishes in about 16 seconds despite roughly 35 seconds of combined subagent LLM time.

  • Each subagent's model call is nested under its Agent span and carries an agent_id, so you can tell the four apart.

  • agent_id is opaque. There's no agent.name or skill.name. The trace tells you four subagents ran and how long each took, not which topic each was given.

  • Spans carry token counts but no USD cost. Each claude_code.llm_request has input_tokens, output_tokens, cache_read_tokens, and cache_creation_tokens, but no USD figure.

  • The write is a separate, later interaction. Trace 6 has no Agent spans: one claude_code.llm_request of about 23 seconds produces the combined markdown, then a short Write. The two ScheduleWakeup spans are background coordination.

TIP: From the trace you get the four subagent calls, each with an agent_id, token counts, and timing, but no span says which topic a subagent was handed. In contrast, Metrics can provide attribution by model, effort, and skill.

CAUTION: user_prompt is redacted by default on interaction spans. OTEL_LOG_USER_PROMPTS=1 disables this and logs raw prompt text. Avoid enabling it in multi-user/tenant environments since it exposes prompt content to anyone with access to the telemetry backend.

Conclusion

This guide was an end-to-end walkthrough of observability in Claude Code, enabling its telemetry and collecting each of the three signals in a local backend for analysis.

Metrics give you the ability to dissect cumulative cost and usage readings by attribute over a chosen time period. That matters most in a shared or multi-tenant setup, where cost isn't tied to a single owner and someone still has to account for it.

Logs are records of individual events, useful for digging into exactly what changed during one, like compaction.

Traces show how one prompt expands into subagents and model calls, with timing and token counts on each. That's the starting point for debugging or tightening a complex or multi-agent prompt, though the spans don't yet record which prompt or skill drove a given call.

Some of this telemetry is behind the Enhanced Telemetry beta, so span names and attributes can still change, and gaps like per-call attribution may close as it matures. It's worth re-checking the monitoring docs as the surface settles.

References



Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Adding Properties with extension methods in C# 14

1 Share

Thanks to @topuswork for starting a funny discussion about extension properties on a recent post I made: "Adding static getter thanks to extensions". Therefore let's discuss two ridiculous ideas to add properties to existing types using extension methods in C# 14.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Podcast: Personality Over Skillset: How Adam Wachtel Builds Engineering Teams

1 Share

In this podcast, Shane Hastie, Lead Editor for Culture & Methods, spoke to Adam Wachtel, CTO at Click Boarding, about hiring for personality and problem-solving over pure skillset, turning around a platform and team in crisis, and how AI is reshaping team size and composition without ending SAAS.

By Adam Wachtel
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories