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, publishable keys, and ingest endpoints 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.