When something breaks in production, the useful question is rarely "write me a fix". It is "why is this happening", and answering it means looking at things: the logs from the last hour, a few rows in the orders table, the status of a container, the code path that produced the error. An AI agent can do a lot of that looking quickly. The problem is that the same access that lets it look also lets it touch.
So we built troubleshoot sessions around one rule: the agent is never trusted to stay read-only because it was told to. Every way it can reach your environment is a narrow function that can only do the safe thing. This post explains how that works and what it still leaves to you.
A chat that investigates, then stops
A troubleshoot task is not a development run. There is no spec and no pull request. You get a conversation: each message you send becomes one turn in which the agent investigates and answers, showing the evidence it found rather than asserting a cause. Follow-up questions resume the same agent session, so it keeps what it already read.
When you have seen enough, you ask it to conclude. It writes a diagnosis in a fixed shape: root cause, evidence, impact, proposed fix and how to verify it. From that diagnosis you can create one bugfix task. That task lands as a draft and goes through the normal pipeline, with a spec, an approval, verification, self-review and a security scan, before any pull request opens. The investigation never becomes the fix.
The code: plan mode and three tools
The project's repositories are cloned into a sandbox for the session. The agent runs Claude Code in plan mode, which refuses edits, and its tool set is limited to Read, Glob and Grep. Bash is deliberately absent. A command against the live environment has to go through the allowlist described below, and a command against the sandbox is not something a read-only investigation needs.
The environment: opt-in, per capability
Reaching a live environment goes through three tools, and each one is switched on separately per environment. You can expose only logs on production and allow SQL and a few commands on staging. A capability that is switched on but not fully configured is not granted at all. Whether a tool is available is decided on our side from the environment's settings, so an agent that asks for a tool the environment does not expose is refused, whatever its prompt said.
Logs. The agent can ask for recent logs with a filter and a time window. It cannot choose where they come from: the endpoint is part of the environment's configuration. We support a plain HTTP endpoint, Grafana Loki, Sentry and GitHub Actions run logs, with a cap on the number of lines per call.
SQL. One statement per call, and it has to start with SELECT, WITH, EXPLAIN, SHOW or TABLE. Before it reaches the database, comments and string literals are stripped and the rest is scanned for any write, DDL or session verb, so a writing statement hidden inside a WITH clause is caught. Functions that read files or reach other databases, such as dblink, are refused. An optional table allowlist limits which tables a query may read. Then the transaction is opened READ ONLY, with a statement timeout and a row cap.
Commands. Only commands that match an allowlist you write, as literal prefixes: "docker logs" permits reading a container's logs, not "docker exec". Commands run without a shell, so semicolons, pipes and backticks are never interpreted, and we refuse them outright because their presence means the agent expected something else to happen. Shells, interpreters and wrappers that would run anything (bash, python, xargs, sudo, find and the like) cannot be allowlisted. Follow flags such as -f are refused because they never finish. An empty allowlist permits nothing.
A budget per question
The agent calls a tool by writing a small JSON block in its answer; we run the call, feed the result back and let it continue. One question may use at most twelve tool calls. After that it is told to answer with what it has and say what it would check next. The limit bounds cost, and it bounds how much of a live system a single question can touch. A refused call is not an error that ends the turn; the refusal is shown to the agent so it can choose something it is allowed to do.
Every call, its input and a preview of its output are recorded on the turn. That list is what you see in the conversation as the investigation trail, and it is the record of what the agent touched in your environment.
Where it runs matters
Turns can run on our platform or on your own runner. On a self-hosted runner, reaching your private network is the point. On our platform it would be dangerous, so the rules there are stricter: commands only go over ssh, log endpoints, database hosts and ssh targets must be public addresses, and the connection is pinned to the address that was checked. In both cases commands run with a minimal environment and a throwaway home directory, so even an allowed command cannot print the executor's own secrets. Environment secrets are encrypted at rest and only decrypted for the turn that needs them.
What it does not guarantee
- The SQL check is a filter, not a parser of your database's full grammar. The guarantee that matters is a database role with SELECT-only grants. Give the session one; then the statement check, the read-only transaction and the grant are three independent layers.
- The filter errs towards refusing. A column that happens to be called "comment" or "set" trips it, and the agent has to find another way to ask.
- Database access is PostgreSQL only.
- A read-only query can still be expensive. The timeout and row cap limit that; pointing the session at a replica is better.
- Logs and query results are sent to the model. If your logs contain personal data, decide per environment whether logs should be exposed at all.
A sensible first setup
Start with code only, which needs no environment at all, and see how far the agent gets from a stack trace and the repository. Then connect logs for production and a read-only role on a replica. Leave commands for staging until you know which three or four you actually reach for when something breaks. The full list of switches is on the troubleshooting page, and if production should never be reached from outside your network, run the sessions on a self-hosted runner.