# Environment variables

> Every environment variable Tinka reads or sets, grouped by purpose, with what reads it, its values and its default.

This guide covers the environment variables Tinka reads and sets. You will learn how to:

- Set the Mode and the directory `.env` files are read from
- Configure an App made from the skeleton Starter kit
- Recognize the variables `act` sets for the App, which you never set yourself
- Turn Agent mode on or off, and know which variables detect an agent
- Turn colors off in terminal output
- Rewrite golden files in tests
- Let Go download Tinka during the alpha

## Overview

Tinka reads few environment variables of its own, and they come in three kinds:

- **Core reads them.** The `env` package, `Serve` and every Command read these, whatever Starter kit the App came from.
- **Your App reads them.** The skeleton Starter kit's `config/*.go` files read `PORT`, `APP_KEY` and the rest. They are your App's code: rename them, add your own, or drop them.
- **`act` sets them.** `act serve` and Forwarding pass these to the processes they start. Setting them yourself breaks what they coordinate.

Variables read through an Env's getters, such as `e.Int("PORT", 3000)`, come from the real environment or from the Mode's `.env` files, and the real environment wins. Every other variable on this page is read from the real environment only, so setting it in a `.env` file has no effect. [Configuration & Environment](../configuration.md) explains how `.env` files are found, merged and validated.

## The Mode and .env files

The `env` package reads these two before anything else, because they decide which `.env` files the App reads. Both must come from the real environment.

::::options

:::option{name="APP_ENV" type="string"}
The App's Mode: `development`, `test` or `production`, in lowercase. Unset or empty means `development`. Any other value is a problem that `e.Err()` reports, and no `.env` file is read. A `.env` file that sets it to another Mode is reported too, since the value would have no effect. Production reads no `.env` file at all.

Commands read it the same way, through `env.ParseMode`. A destructive Command asks for confirmation in production and for any value `ParseMode` refuses, so a mistyped Mode never skips the guard. `act generate:key` prints the key instead of writing it in production, and fails with `invalid_app_env` for a value it doesn't know. `act about` shows the Mode, or `invalid APP_ENV` with the value it refuses.
:::

:::option{name="ENV_PATH" type="string"}
The directory the `.env` files are read from. A relative path is resolved against the working directory, or against `env.Dir` when the App passes it; an absolute path is used as it is. Unset, the files are read from that directory itself. `act generate:key` writes the key into the `.env` file it finds there.
:::

::::

Any variable read through a getter also has a file form: when `KEY` is missing and `KEY_FILE` is set, the getter reads the value from the file `KEY_FILE` names. See [Reading values from files](../configuration.md#reading-values-from-files).

## The skeleton Starter kit's variables

An App created with `tinka new --kit skeleton` reads these in `config/`, and lists them in `.env.example`. `tinka new` copies `.env.example` to `.env` with a new `APP_KEY`, and writes a `.env.test` with a separate test key and `LOG_LEVEL=error`. The core Starter kit reads none of them: its `tinka.New(tinka.HTTPConfig{})` listens on `:3000`.

::::options

:::option{name="PORT" type="int"}
The port the App listens on, on every interface (`HTTPConfig.Addr` becomes `":" + PORT`). Defaults to `3000`. Under `act serve` it has no effect, because `act serve` passes the App its listener.
:::

:::option{name="APP_URL" type="URL"}
Where people reach the App, for the links it builds. An absolute URL with a scheme and a host. Defaults to `http://localhost:3000`.
:::

:::option{name="APP_KEY" type="secret"}
The key that signs and encrypts cookies and signed URLs. Required: without it the App stops at boot, before it serves or runs a Command. Generate one with `act generate:key`. See [The APP_KEY](../configuration.md#the-app-key).
:::

:::option{name="APP_DEBUG" type="bool"}
Adds a server error's cause, and a panic's stack, to its response (`HTTPConfig.Debug`). Defaults to `false`; `.env.example` sets it to `true` for your machine. Production reads no `.env` file, so it stays off there unless you set it in the real environment.
:::

:::option{name="LOG_LEVEL" type="string"}
The least severe level the logger writes: `trace`, `debug`, `info`, `warn`, `error` or `fatal`, in lowercase. Defaults to `info`. Any other value is a problem the App reports at boot, with the others. See [Log levels](../guides/digging-deeper/logger.md#log-levels).
:::

:::option{name="LOG_FORMAT" type="string"}
How log records are written: `json`, `text` or `pretty`, in lowercase. Defaults to `pretty` in development and `json` in the other Modes. The kit's tests write `text` to the test's output whatever it says.
:::

::::

The kit's `tests.App` builds the App in the `test` Mode whatever `APP_ENV` says, so tests load `.env.test` over `.env`, and never `.env.local`.

## Variables act sets for the App

`act serve` and Forwarding start other processes and pass them these variables. They are internal: never set them yourself, in a shell or in a `.env` file.

::::options

:::option{name="TINKA_DEV" type="1"}
Set by `act serve` on the App it runs. `Serve` then skips `HTTPConfig.DrainDelay` on shutdown, since no load balancer needs draining, so restarts stay fast.
:::

:::option{name="LISTEN_FDS" type="int"}
The socket activation protocol, set by `act serve` on Unix systems such as Linux and macOS, and by systemd in production. When it's set, and `LISTEN_PID` is unset or names the App's process, `Serve` serves on the listener already open as file descriptor 3 instead of `Addr`. It must be `1`: any other number makes `Serve` fail. `Serve` unsets both variables, so programs the App starts don't take the listener. See [Socket activation](../guides/concepts/application-lifecycle.md#socket-activation).
:::

:::option{name="LISTEN_PID" type="int"}
The process the listener in `LISTEN_FDS` is meant for. systemd sets it; `act serve` doesn't.
:::

:::option{name="TINKA_DEV_ADDR" type="string"}
Set by `act serve` on Windows, which can't pass a listener to another process: the address `Serve` listens on instead of `Addr`. `Serve` reads it on every system whenever it takes no listener from `LISTEN_FDS`.
:::

:::option{name="TINKA_DEV_TOKEN" type="secret"}
Set by `act serve` on Windows, next to `TINKA_DEV_ADDR`. The App proves with it that it, and not another program that bound the port first, is answering. `Serve` unsets it at once.
:::

:::option{name="TINKA_LAUNCHED" type="1"}
Set by Forwarding on the process it starts: the App's binary, the version of `act` an App's `go.mod` pins, or the Tinka repo's own tooling. It stops that process from forwarding again.
:::

:::option{name="GOWORK" type="off"}
Set to `off` for every `go build` and `go test` that `act` runs, and shown in the commands `act build` and `act test` print, so a `go.work` file elsewhere in the tree can't change what the App is built from.
:::

:::option{name="CGO_ENABLED" type="0 or 1"}
Set by `act build`: `0`, or `1` with `--cgo`. `--target` sets `GOOS` and `GOARCH` the same way. These replace your own values for that build.
:::

::::

`act` caches the App's binary and builds it again when its sources change, or when one of these Go variables does: `GOFLAGS`, `GOOS`, `GOARCH`, `GOAMD64`, `GOARM64`, `GOEXPERIMENT`, `CGO_ENABLED`, `CC`, `GOTOOLCHAIN`, `GOPROXY` and `GOPRIVATE`.

## Agent mode and agent detection

Every Command decides between output for people and a single JSON result. `--json` always wins; otherwise these variables decide, in order. See [When Agent mode turns on](agent-mode.md#when-agent-mode-turns-on).

::::options

:::option{name="TINKA_AGENT" type="bool"}
`1` or `true` forces Agent mode, and `0` or `false` turns it off, in any case. Any other value is ignored, and agent detection decides.
:::

:::option{name="AI_AGENT" type="string"}
The name of the agent running the Command, such as `claude-code`. Any name turns Agent mode on, and so do `1` and `true`. An empty value, `0` and `false` count as unset, so the variables below are still checked: use `TINKA_AGENT=0` to turn Agent mode off.
:::

:::option{name="AGENT" type="string"}
The same as `AI_AGENT`, checked after it.
:::

::::

When neither names an agent, Tinka turns Agent mode on if any of the variables below is set, to any value, even an empty one; only `TERM_PROGRAM` needs the value shown. The last four also appear in terminals that people type into, because editors set them for their integrated terminal, so they only count when stdin is not a terminal.

- **Cursor**: `CURSOR_AGENT`
- **Grok**: `GROK_PLUGIN_ROOT`, `GROK_PLUGIN_DATA`
- **Gemini CLI**: `GEMINI_CLI`
- **Codex**: `CODEX_SANDBOX`, `CODEX_CI`, `CODEX_THREAD_ID`, `CODEX_SANDBOX_NETWORK_DISABLED`
- **Augment**: `AUGMENT_AGENT`
- **OpenCode**: `OPENCODE_CLIENT`, `OPENCODE`
- **Amp**: `AMP_CURRENT_THREAD_ID`
- **Claude Cowork**: `CLAUDE_CODE_IS_COWORK`
- **Claude Code**: `CLAUDE_CODE_CHILD_SESSION`
- **GitHub Copilot**: `COPILOT_CLI`, `COPILOT_MODEL`, `COPILOT_ALLOW_ALL`
- **Antigravity**: `ANTIGRAVITY_AGENT`, `ANTIGRAVITY_CLI_ALIAS`
- **Pi**: `PI_CODING_AGENT`
- **Junie**: `JUNIE_DATA`, `JUNIE_SHIM_PATH`, `MATTERHORN_SESSION_ID`
- **Kiro**: `KIRO_AGENT_PATH`
- **Kimi**: `KIMI_PLUGIN_ROOT`
- **Cline**: `CLINE_ACTIVE`
- **Goose**: `GOOSE_TERMINAL`
- **OpenClaw**: `OPENCLAW_SHELL`
- **ZCode**: `ZCODE_ENV`
- **Devin**: a file at `/opt/.devin`, rather than a variable
- **Claude Code**, without a terminal: `CLAUDECODE`, `CLAUDE_CODE`
- **Cursor**, without a terminal: `CURSOR_TRACE_ID`
- **Replit**, without a terminal: `REPL_ID`
- **Kiro** and **Windsurf**, without a terminal: `TERM_PROGRAM=kiro` or `TERM_PROGRAM=windsurf`

## Terminal output

Commands style their output only when stderr is a terminal. Two variables turn the styling off. See [Control colors](../guides/command-line/terminal-output.md#control-colors).

::::options

:::option{name="NO_COLOR" type="string"}
Any non-empty value turns colors off, in Commands' output and prompts and in the logger's `pretty` format.
:::

:::option{name="TERM" type="string"}
`dumb` turns colors off in Commands' output, and makes prompts that move the cursor ask for a number instead. The logger's `pretty` format doesn't read it.
:::

::::

An App's own Command can also read a flag from a variable you choose with `f.Env`, such as `f.Env("batch-size", "IMPORT_BATCH_SIZE")`, and the command line wins over the variable. Tinka's own Commands bind no flag to a variable. See [Read flags from the environment](../guides/command-line/flags.md#read-flags-from-the-environment).

## Tests

::::options

:::option{name="TINKA_UPDATE" type="1"}
Exactly `1` makes golden-file assertions write the file instead of comparing with it: `AssertGolden` in `tinkatest`, for a response body, and `clitest.Signatures`, for the signatures of an App's Commands. Review the change before committing the file. See [Assert on the response](../guides/testing/http-tests.md#assert-on-the-response) and [Keep signatures stable](../guides/testing/commands.md#keep-signatures-stable).
:::

::::

## Where act keeps its files

`act` records trusted Apps in `tinka/trusted-apps` under your user config directory. It keeps the binaries it builds for Forwarding, and the locks of `--isolated` runs, under `tinka/` in your user cache directory. Go finds both directories from the environment, the config directory first:

- **Linux**: `$XDG_CONFIG_HOME`, or `$HOME/.config`, and `$XDG_CACHE_HOME`, or `$HOME/.cache`
- **macOS**: `$HOME/Library/Application Support` and `$HOME/Library/Caches`
- **Windows**: `%AppData%` and `%LocalAppData%`

When Go can't find the cache directory, Forwarding fails with `no_cache_dir`. See [Error codes](error-codes.md#forwarding).

## Installing during the alpha

During the alpha the Tinka repository is private, and Go's public module proxy can't download it. Set Go's `GOPRIVATE` so the go command downloads every `github.com/mucolabs` module straight from GitHub, bypassing `GOPROXY` and the checksum database for them:

```sh
go env -w GOPRIVATE=github.com/mucolabs/*
```

`git` must also be able to authenticate to GitHub. Without both, `go install` fails, and `tinka new` fails with `install_failed` after creating the App. See [Installation](../installation.md).

See also: [Configuration & Environment](../configuration.md), [Agent mode](agent-mode.md), [Error codes](error-codes.md), [Commands](commands.md), [Development environment setup](../development.md)
