---
title: Troubleshooting
description: Diagnose the failures HermesOS reports most often, and what to change in each case.
url: https://hermesos.thally.app/troubleshooting
---

# Troubleshooting

Diagnose the failures HermesOS reports most often, and what to change in each case.

Work from the error's `code` rather than its message. Codes are stable;
messages are not.

## A tool call is refused

### `PERMISSION_DENIED`

```text
fs.write requires the "fs:write" permission, which was not granted
```

The tool declares a permission the host did not grant. This is a **wiring
fact**, not a runtime decision — the same call will be refused every time until
the grant changes.

**Fix it** at the composition root:

```ts
const granted = PermissionSet.none().grant('fs:read', 'fs:write');
```

Retrying with different arguments will never help, and the message says so to
stop a model from trying.

> **Tip:**
  Granting `fs:*` covers every action in the `fs` domain. Note that `*:read`
  does **not** work — wildcards apply to domains, not actions.

### `TOOL_NOT_FOUND`

The name is not registered. The message lists the tools that are, which is
usually enough to spot a typo or a toolset that was never `use`d.

## A tool call fails validation

### `INPUT_INVALID`

The arguments did not match the tool's input schema. The message names the
field and what was wrong with it:

```text
fs.write was called with invalid input: "path" must be a string
```

If a model keeps sending the wrong shape, add an `examples` entry to the tool.
A worked example resolves ambiguities a JSON Schema cannot express.

### `OUTPUT_INVALID`

**This is a bug in the tool, not in the call.** The tool returned something its
own declared output schema rejected.

```text
fs.write returned a value that does not match its own output schema: must be a
string. This is a fault in the tool, not in how it was called
```

Do not retry. Fix the tool's `execute` or its `output` schema.

## The process will not start

### `INVALID_DEFINITION`

Thrown at module load, so it is a crash on boot rather than a failure on first
call. Check for an empty name, an empty description, or a non-semantic
`version`.

## A mission hangs or will not stop

A tool that ignores `ctx.signal` keeps running after its mission is cancelled.
Pass the signal into whatever performs the real work.

A tool that uses `setTimeout` instead of `ctx.clock.sleep(ms, ctx.signal)` will
also keep a timer alive, which can stop a short-lived process from exiting even
after it has done its work correctly.

## A model calls tools with the wrong arguments

Almost always a metadata problem rather than a model problem:

1. **No input schema.** The model is told the tool exists and nothing about its
   arguments, so it guesses. Declare `input`, or `nothing()` if it truly takes
   none.
2. **No examples.** Add one. It is the highest-leverage field in a tool
   declaration and the one most often skipped.
3. **A thin description.** Run `auditTool` — it flags descriptions too short to
   choose by.

## The build or test suite fails locally

```bash
pnpm install
pnpm build
pnpm test
```

`pnpm build` must run before `typecheck` and `test`, because packages resolve
each other through their built output. CI builds the workspace first for the
same reason.

## Getting more detail

Every tool call receives a structured `logger` on its context, tagged with
`missionId` and `taskId`. Filtering a log by `missionId` gives you one mission's
full story.