# Troubleshooting.

Every diagnostic code `dif validate` emits, what it means, and how to fix it.

## Diagnostic codes

| code | level | meaning | fix |
| --- | --- | --- | --- |
| `E001` | error | Frontmatter failed to parse: missing `---` delimiters, invalid YAML, or a required field is missing. | Diagnostic includes the line and column. Open the file at that location and fix the YAML. |
| `E003` | error | `owner` doesn't look like an email: no `@`, no domain, or whitespace. | Use a syntactically valid email. `name@example.com`. |
| `E004` | error | `surface` doesn't resolve to a loaded surface file. | Create `dif/surfaces/<name>.md`, or change the `surface:` field to one that exists. |
| `E005` | error | Variant weights don't sum to 100. | Adjust the weights so they total 100. |
| `E006` | error | An audience predicate references an attribute that isn't declared in `dif/config.yaml`. | Add it to `audience_attributes`, or remove the predicate. |
| `E007` | error | Two active experiments target the same surface without a shared `exclusion_group`, and their audiences aren't provably disjoint. | Three options: (1) add the same `exclusion_group:` to both, (2) narrow one audience so the analyzer can prove disjointness (e.g. `country: US` vs `country: UK`), or (3) move one to a different surface. |
| `E008` | error | A declared audience attribute has no resolver file in `dif/audiences/`. | Create `dif/audiences/<name>.ts`, or run `dif scaffold-audiences` for the starters. |
| `W001` | warning | Source code contains a `dif("<id>", ...)` call referencing an experiment that doesn't exist (or isn't active). | Either create the experiment or remove the `dif()` call. Warnings don't fail the build. |
| `W002` | warning | A resolver file in `dif/audiences/` has no matching declaration in `config.yaml`. | Declare it under `audience_attributes`, or delete the unused resolver. |

Diagnostics include the file, line, and column of the offending content. The CLI prints them rustc-style:

```
error E005 dif/experiments/active/bad.md:1:1: variant weights sum to 80, expected 100
  help: Distribute the variants so the weights total 100.
```

Use `--json` if you'd rather post-process them.

## FAQ

### What's the bucketing algorithm?

SHA-256 over a per-experiment salt concatenated with the user id, mod 10000 → a bucket in `[0, 10000)`. Variant selection walks `variants` in declared order, accumulating weight; the first variant whose cumulative crosses the bucket wins. Bucketing is deterministic for a given salt and user id, and Rust and TypeScript share a cross-language fixture in the repo test suite: Rust generates it, TypeScript verifies, and CI runs both on every PR.

### Can I run two experiments on the same surface?

Yes. Either share an `exclusion_group` (the runtime resolves with priority: earliest `created` wins) or write audiences the disjointness analyzer can prove don't overlap (`country: US` vs `country: UK`, for example). Neither, and `dif validate` refuses with `E007`.

### Do I need a resolver for every audience attribute?

Yes. Each attribute referenced by an active experiment must be declared in `dif/config.yaml` _and_ have a synchronous resolver at `dif/audiences/<name>.ts` that returns a compatible scalar. Missing the declaration is `E006`; missing the resolver is `E008`. `dif build` bundles only the resolvers active experiments actually use into `dif/generated/audiences.ts`.

### Why does `dif/context.json` change every build?

It carries a generation timestamp. That's intentional: agents read freshness from it. The file lives outside the gitignored `dif/generated/` directory so it's checked in. In CI, rebuilding it will normally produce a diff even when experiment inputs are unchanged; don't assert a clean context diff unless you normalize the timestamp.

### Where does `git config` come into it?

`dif new` infers `owner` from `git config user.email` when `--owner` isn't passed. If neither is set, the command errors out. CI runs without a git identity should pass `--owner` explicitly.

### Is the generated `dif/generated/client.ts` meant to be committed?

No, by default. `dif init` writes a `dif/.gitignore` covering `generated/`. Teams that want PR-visibility of the generated code can flip `build.commit_generated` (a reserved field today).

### What about Windows?

The Rust CLI ships a Windows binary; install via the npm wrapper (`npm install -g @dif.sh/cli`) or download the `dif-x86_64-pc-windows-msvc.zip` from the GitHub Release page. The shell installer at `dif.sh/install.sh` is POSIX-only. Use WSL or the wrapper instead.

### What's the cloud offering? Cloud

dif.sh Cloud mirrors your repo's spec, ingests `dif.track()` and exposure events, and computes lift / CI / SRM on top. It's strictly additive: connect at runtime through `dif.init({ project, publishableKey, apiUrl })`, the OSS CLI keeps working offline, and writes back to git always happen as PRs. See the [tracking](https://dif.sh/docs/sdk/#track), [publishable keys](https://dif.sh/docs/sdk/#publishable-keys), and [ingest endpoints](https://dif.sh/docs/sdk/#ingest) sections for the integration.

### How do I uninstall?

Delete the `dif/` directory. Experiments and surfaces are just markdown files in your repo: leave them, delete them, archive them, whatever you'd do with any other source. Remove `@dif.sh/sdk` from your app's `package.json` and the `dif(...)` call sites are dead code your bundler will strip.

> // missing? If a diagnostic code or a question isn't covered here, open an issue at [github.com/dif-sh/dif/issues](https://github.com/dif-sh/dif/issues). Real bug reports → real diagnostic entries → real fixes. That's the loop.
