Skip to content

Configuration

Kenn Forge reads ~/.kenn/forge/config.toml. Set KENN_FORGE_HOME to move both config and app data. Most users only need repositories, credentials, and optional modes.

Use Settings for routine changes. Edit TOML for provider hosts and advanced options. Restart Forge after changing startup settings.

Repositories

Faster GitHub updates

An optional shared relay receives messages from GitHub when something changes. Forge stays connected to the relay, receives each change as it happens, and fetches the changed items using its own GitHub credentials. Add the private feed URL supplied by the person running the relay, then restart Forge:

[relay]
url = "https://relay.example.com"

Use the HTTPS origin without /activity or a webhook path. In a fleet, add this setting to the hub. An empty URL disables the relay. Normal syncing continues. See Faster GitHub updates for setup, access, and a walkthrough to check that it works. The relay supports GitHub.com. With workflow_run webhooks enabled, the relay sends at most one checks hint per repository/PR per minute. Forge refreshes its checks on receipt, within the usual API limits. No extra setting is needed.

Repository selection

A GitHub repository on github.com needs only its owner and name:

[[repos]]
owner = "team"
name = "service"

You can paste a common HTTPS or SSH repository URL into owner or name. Forge normalizes it.

Set the provider and host for other services or self-hosted instances:

[[platforms]]
type = "gitlab"
host = "gitlab.example.com"
token_env = "GITLAB_EXAMPLE_TOKEN"

[[repos]]
platform = "gitlab"
platform_host = "gitlab.example.com"
owner = "group/subgroup"
name = "project"
repo_path = "group/subgroup/project"

Repository identity includes platform, platform_host, owner, and name. Keep repo_path when the provider uses nested namespaces or canonical casing.

Self-hosted Gitea transport

Gitea uses https://<host> by default. Set an explicit API URL when a trusted private deployment serves Gitea over HTTP:

[[platforms]]
type = "gitea"
host = "gitea.example.test:3000"
base_url = "http://gitea.example.test:3000"
allow_insecure = true
token_env = "GITEA_PRIVATE_TOKEN"

host remains the repository identity and must match Gitea's advertised clone URLs. allow_insecure acknowledges that API and Git credentials can travel without TLS. Forge rejects plain-HTTP or mismatched clone URLs before using them.

To hide a repository from lists and pickers without removing it, open the gear menu on its Settings row and choose "Hide from UI". Syncing continues and direct links keep working; choose "Show in UI" to bring it back. Hiding applies to exact repositories, not glob patterns.

Credentials

Credentials are scoped by provider and host:

github_token_env = "KENN_FORGE_GITHUB_TOKEN"

[[platforms]]
type = "forgejo"
host = "forge.example.com"
token_env = "FORGE_EXAMPLE_TOKEN"

[[repos]]
platform = "forgejo"
platform_host = "forge.example.com"
owner = "team"
name = "private-service"
token_file = "~/.kenn/forge/tokens/private-service"

An exact repository token_file or token_env takes priority over broader credentials. Empty files and variables are skipped. Token files are read on each request, so replacing a file atomically rotates that credential.

When no token source is declared for a host, Forge reuses the credential the provider's CLI stores for that host:

  • GitHub: gh auth token --hostname HOST. The unscoped fallback applies only to github.com.
  • GitLab: the token glab holds for the host, from its config file or the operating-system keyring. GITLAB_TOKEN and similar variables are not used here; declare token_env to use one.
  • Forgejo and Gitea: the token fj (forgejo-cli) stores for the host in its keys file. Expired OAuth logins are skipped; run any fj command against the host to refresh them.

Authenticate a host with the matching CLI:

gh auth login --hostname HOST
glab auth login --hostname HOST
fj --host HOST auth login

Public-host defaults are:

  • GitHub github.com: KENN_FORGE_GITHUB_TOKEN, then the GitHub CLI.
  • GitLab gitlab.com: no implicit variable, then the GitLab CLI.
  • Forgejo codeberg.org: KENN_FORGE_FORGEJO_TOKEN, then the Forgejo CLI.
  • Gitea gitea.com: KENN_FORGE_GITEA_TOKEN, then the Forgejo CLI.

Grant read access for monitoring. Add write access only for comments, reviews, state changes, edits, or merges.

GitHub credentials by owner

Map fine-grained PATs to owners when one token cannot read every repository:

[[github_owner_tokens]]
owner = "org-a"
token_env = "KENN_FORGE_GITHUB_TOKEN_ORG_A"

[[github_owner_tokens]]
owner = "org-b"
token_file = "~/.kenn/forge/tokens/org-b"

Owner mappings are configured in TOML only.

GitHub selects credentials by host and owner. Exact repository credentials win. A covered GitHub App handles reads. Owner PATs handle uncovered reads and user-attributed writes. Host credentials and the GitHub CLI follow.

PATs for the same GitHub user share one rate limit and sync budget. Different users and App installations have separate budgets. Restart after routing an owner to a PAT from a different GitHub user.

GitHub App reads

GitHub Apps are an advanced option for more sync capacity. Forge uses an App's installation quota for sync reads, leaving your personal quota for other tools and actions.

An App can read only the repositories covered by its installation and granted permissions. Owning the App does not grant repository access. An installation in one organization does not cover another organization's repositories. Repositories outside that installation still need another credential route.

The API quota belongs to each installation. Repositories in that installation share it; installing an App does not raise your personal token's rate limit.

On GitHub.com, installation REST limits start at 5,000 requests per hour and can scale to 12,500 with repository and organization size. Installations on GitHub Enterprise Cloud organizations get 15,000. See GitHub's installation rate limits for the current rules.

Creating an organization-owned App requires organization owner access or permission to manage all of the organization's GitHub Apps. Installation may also require organization approval. If you do not have that access, ask an organization owner to help with setup. See GitHub's App registration requirements.

Replace your-org with your organization:

kenn-forge-github-app create --org your-org
kenn-forge-github-app list
kenn-forge daemon restart

The create command opens a browser for App creation and installation. Choose the organization and repositories Forge should read. Omit --org to create a personally owned App. If you need to finish installation later, run kenn-forge-github-app install --owner your-org.

Forge's local sync budget still applies to ordinary App sync reads. Raise sync_budget_per_hour if the local ceiling stops sync while the installation has quota left.

For a busy historical archive, create a second App with its own installation budget:

kenn-forge-github-app create --org your-org --role archive
kenn-forge-github-app install --app-id <archive-app-id>

The archive App must be installed on the same repository account. Archive reads use it only for repositories it covers; ordinary sync and mutations keep using the normal App/PAT routes. The two Apps must be distinct GitHub Apps. The role is also visible in kenn-forge-github-app list and can be set explicitly as role = "archive" in [[github_apps]].

The CLI writes [[github_apps]] entries. Mutations still use a user PAT. After changing selected repository access on GitHub, run kenn-forge-github-app install again and restart Forge.

Selected repository access is a startup routing snapshot. New grants use the PAT route until refresh. Revoked App access can return 404, and Forge does not retry that response with a PAT because 404 can also mean missing or private.

Sync budget

sync_budget_per_hour limits the API requests Forge spends on live background sync each hour. It defaults to 500. For a large or active repository, raise it in ~/.kenn/forge/config.toml:

sync_budget_per_hour = 3000

Put this at the top level, before any [section] or [[repos]] header. If the key already exists, change its value. Restart Forge to apply it:

kenn-forge daemon restart

The value must be at least 50. Omitting it or setting it to 0 uses the 500-request default; zero does not disable the ceiling.

The same configured limit applies to each budget. GitHub repositories share a budget when they use the same GitHub user or App installation on a host. Other providers share a budget per provider host.

Forge reserves 10% for discovering new and closed issues and pull requests. With a limit of 3,000, optional live work such as detail refreshes stops at 2,700, while discovery can use the full 3,000.

GitHub archive requests reserved against provider quota do not spend this local allowance. Other archive paths can use it. See Archive sync capacity for provider reserves and local-budget behavior.

Raising this value lets Forge use more of your provider quota. It does not increase that quota. Leave room for other tools that use the same account, and check the provider's remaining quota before raising it again. See Local sync ceiling reached for recovery steps.

If GitHub's quota is the bottleneck, set up a GitHub App to move sync reads to an installation quota.

Activity defaults

[activity]
view_mode = "threaded"
time_range = "7d"
hide_closed = false
hide_bots = false
collapse_threads = true
default_branch_retention_days = 90
default_branch_max_commits = 5000

These values set the initial Activity view and local default-branch retention.

App modes

[modes]
activity = true
repos = true
docs = false
actions = false
pulls = true
issues = true
reviews = true
workspaces = true

Actions and Docs default to false; enable them after their provider workflows or local folders are ready. Disabling Actions removes both its top-level page and pull-request menu while leaving provider workflow access available to API clients. Kata integration is contextual rather than a top-level mode. Set any other mode to false to hide it.

Roborev

The Reviews page reads from a separately running Roborev daemon. The default endpoint is http://127.0.0.1:7373:

[roborev]
endpoint = "http://127.0.0.1:7373"
init_managed_clones = true

The endpoint controls the Reviews connection and requires a restart when it changes. init_managed_clones is optional and defaults to false; you can also change it under Settings → Workspaces without restarting. When it is enabled, the endpoint must use loopback HTTP (127.0.0.1, localhost, or [::1]) because Forge passes it to the Roborev CLI during workspace setup.

Set reviews = false under [modes] if you do not want the Reviews page. See Integrations for the current Reviews workflow.

Workspace agents

Forge detects built-in agents on PATH. Add or override an agent with:

[[agents]]
key = "review"
label = "Review Agent"
command = ["review-agent", "--fast"]

You can also edit agents under Settings → Agents.

Quick actions

A quick action creates the workspace for the current pull request or issue, launches one configured agent, and sends a preset prompt as its first message. Quick actions appear behind the lightning icon on the Create Workspace button. Configure them under Settings → Quick actions or in TOML:

[[quick_actions]]
label = "Rebase"
agent = "codex"
prompt = "rebase this pull request onto main"

[[quick_actions]]
label = "Triage"
agent = "opencode"
prompt = """
/triage-pr
question all assumptions
"""

agent is a workspace agent key. Labels must be unique (case-insensitive). Prompts may contain printable text and line breaks, and are limited to 64 KiB, the same rules as an agent's first message. A quick action whose agent is missing or unavailable stays visible but disabled until the agent is fixed.

Workspace terminals and tmux

Workspace terminals and agent sessions run on a dedicated tmux server (socket name kenn-forge), so busy Forge sessions do not contend with your personal tmux server. To inspect or attach from a regular terminal:

tmux -L kenn-forge ls
tmux -L kenn-forge attach-session -E -t <session>

-E keeps variables from your shell (including any exported provider tokens, if you widened tmux's update-environment) out of the session environment, which processes inside the workspace can read.

Set [tmux] command to pick a different socket or wrap the launch; the configured command line is used verbatim:

[tmux]
command = ["tmux", "-L", "kenn-forge"]

Wrapper commands run with a minimal non-secret environment, because the tmux server permanently retains the environment it was started with. Pass any custom data a wrapper needs as command-line arguments rather than environment variables. For the same reason, provider token_env names may not reuse standard terminal variables such as EDITOR or PATH; configuration validation rejects the collision.

Sessions started by versions that used the default tmux server keep running there after an upgrade, but Forge no longer sees them. Reattach or clean them up with plain tmux ls and tmux kill-session.

Docs folders

Register local Markdown folders from the CLI:

kenn-forge docs add-folder --name Notes ~/notes
kenn-forge docs list-folders
kenn-forge docs remove-folder notes

The equivalent config is:

[[doc_folders]]
id = "notes"
name = "Notes"
path = "/Users/you/notes"
daemon = "kata-main"

daemon is optional. Set it when task links in this folder always belong to one Kata daemon.

Kata daemons and repository mappings

Forge reads Kata daemon definitions from $KATA_HOME/config.toml, or ~/.kata/config.toml when KATA_HOME is unset. Daemon credentials and URLs stay in Kata's catalog rather than Forge configuration.

Open Settings → Kata mappings to see which repository each Kata project will use for workspace creation. Add a manual mapping when automatic matching does not pick the right configured repository:

[[kata_projects]]
daemon_id = "kata-main"
project_uid = "widgets"
provider = "github"
platform_host = "github.com"
repo_path = "acme/widgets"

daemon_id scopes the mapping to one daemon. Leave it out only for a mapping that should apply to the same project UID on every daemon. Choose an exact repository identity available in Settings. That repository may have been found through a configured pattern, a tracked repository, or a registered project, but the mapping itself cannot contain a glob.

Server and storage

sync_interval = "5m"
host = "127.0.0.1"
port = 8091
base_path = "/"
  • sync_interval controls provider refreshes.
  • host and port set the listener.
  • base_path adds a URL prefix behind a reverse proxy.
  • data_dir moves app data while leaving config under KENN_FORGE_HOME.

For a trusted reverse proxy or a larger SSE replay window:

allowed_hosts = ["forge.example.com", "proxy.example.com:8091"]
trust_reverse_proxy = true
sse_buffer_size = 256

allowed_hosts accepts exact host and port values. A trusted proxy must present accepted direct and forwarded hosts. sse_buffer_size defaults to 256 and accepts 16 through 16384.

When the reverse proxy preserves the browser's original Host header, keep forwarded-host trust disabled:

allowed_hosts = ["build-a.<tailnet>.ts.net"]
trust_reverse_proxy = false

In this mode Forge validates the raw Host value and ignores forwarded-host headers. This is the simpler choice for a private Caddy or Tailscale proxy that does not rewrite Host.

Set trust_reverse_proxy = true only when every browser request comes through a trusted proxy that supplies an accepted Forwarded or X-Forwarded-Host value. Direct browser requests without that forwarded authority are rejected. Host validation does not consume a forwarded scheme; configure HTTPS at the proxy and use HTTPS origins for federation.

MCP companion

Enable the daemon's optional loopback MCP listener with:

[mcp]
enabled = true
# port = 8092 # defaults to the main backend port plus one
diff_cache_mb = 128

The sessionless Streamable HTTP endpoint is http://127.0.0.1:<resolved-port>/mcp. Authentication follows [api].require_auth. Full diff files use a request-based least-recently-used cache; diff_cache_mb defaults to 128 MiB. MCP listener or cache changes require a daemon restart.

Pull request stacks

[pull_requests]
prefer_github_native_stacks = true
allow_mid_stack_merges = false

Native GitHub stack data improves read-only detection when complete. Branch relationships remain the fallback. Forge does not create or reorder stacks. Mid-stack merges stay blocked by default.

Telemetry

Forge sends limited anonymous telemetry by default: daemon activity, app view names, version, commit, OS and architecture, and an anonymous install ID. It does not send repository names, item content, tokens, usernames, hostnames, or paths.

Disable telemetry with:

TELEMETRY_ENABLED=0 kenn-forge daemon start