Files
PaperClipAI/docs/deploy/environment-variables.md
T
Devin Foley 7551b63ef2 Remove the instance Heartbeats settings page (#12282)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Instance settings collect deployment-wide controls; one of them was
the Heartbeats page, an instance-wide list of scheduler heartbeat agents
with enable/disable toggles
> - The same controls live on each agent's own configuration surface, so
the standalone list duplicates them, and its framing no longer matches
how heartbeat agents are managed
> - Keeping a settings view that no longer makes sense costs every
deployment navigation noise and maintenance
> - This pull request removes the page, its route, its navigation
entries, and its hidden-settings key for all deployments
> - The benefit is a smaller, coherent settings surface, with operator
hidden-settings lists that still mention the retired key continuing to
work unchanged

## Linked Issues or Issue Description

No public issue exists; describing the issue inline per the enhancement
template:

**What existing behavior does this improve?**
The instance settings surface — specifically the Settings → Heartbeats
page, which listed scheduler heartbeat agents instance-wide with
enable/disable toggles. The view no longer makes sense as a standalone
settings page: the same controls are available on each agent's
configuration surface, and the instance-wide list framing does not match
how heartbeat agents are managed.

**Subsystem affected**
Cross-cutting: `ui/` (page, route, navigation), `packages/shared`
(settings-visibility registry), docs.

**Current behavior**
The page renders at `/company/settings/instance/heartbeats`, appears in
the settings sidebar and tab bar, and is hideable by hosting operators
via the `instance.heartbeats` key of `PAPERCLIP_HIDDEN_SETTINGS`.

**Proposed behavior**
The page, route, and navigation entries are removed for every
deployment. The `instance.heartbeats` registry key is retired; operator
lists that still send it are logged and ignored, so mixed-version fleets
keep working. Remembered settings paths pointing at the old page remap
to the settings root. Heartbeat APIs are unchanged.

**Reason and benefit**
A smaller, coherent settings surface with no duplicated controls; less
navigation noise and maintenance for every deployment.

**Breaking changes**
None functional. Bookmarks and remembered paths to the removed page land
on the settings root; `PAPERCLIP_HIDDEN_SETTINGS` lists that still
include `instance.heartbeats` log a warning and are otherwise honored
unchanged.

## What Changed

- Deleted `ui/src/pages/InstanceSettings.tsx` (the Heartbeats view) and
its route in `ui/src/App.tsx`.
- Removed the sidebar entry (`CompanySettingsSidebar`) and tab-bar item
(`CompanySettingsNav`).
- Removed `"/heartbeats"` from the remembered-settings-path allowlist;
remembered heartbeats paths now remap to the settings root.
- Retired the `instance.heartbeats` key from the shared
settings-visibility registry and the environment-variables doc;
documented that retired keys are ignored with a warning.
- Dropped the now-unused UI client wrapper for the instance
scheduler-agent list (`heartbeatsApi.listInstanceSchedulerAgents`); the
server endpoint stays.
- Removed the unused `schedulerHeartbeats` query key.

## Verification

- `npx vitest run packages/shared/src/settings-visibility.test.ts
ui/src/lib/instance-settings.test.ts
ui/src/components/CompanySettingsSidebar.test.tsx
ui/src/components/access/CompanySettingsNav.test.tsx` — 24 tests
passing.
- Full `ui` vitest suite: 4426 tests, 4 failures — all in files this PR
does not touch; 3 were load-induced timeouts that pass on rerun, and
`OnboardingWizard.test.tsx` "renders instead of throwing when the
browser denies storage access" fails identically on a clean master
checkout (pre-existing).
- `pnpm --filter @paperclipai/ui typecheck` and `pnpm --filter
@paperclipai/shared typecheck` — clean.
- Merged `master` to clear a conflict (see below) and re-ran the four
focused suites (24 passing), `ui/src/App.test.tsx` and
`ui/src/plugins/bridge.test.ts` (22 passing), and both typechecks — all
clean. Full CI is green on the merge commit.

## Merge With master

`master` gained the `company` → `organization` copy pass (#12243), which
reworded strings inside `ui/src/pages/InstanceSettings.tsx` — the page
this branch deletes — producing a modify/delete conflict. Resolved by
keeping the deletion: the page is going away, so the rewording of its
copy has nothing to apply to. Every other file merged cleanly, and
`master`'s rewording in `App.tsx`, `App.test.tsx`, and
`CompanySettingsSidebar.tsx` sits away from this branch's structural
removals, so both changes survive. The net diff against `master` is
unchanged from the pre-merge review: the same 13 files, 23 insertions,
330 deletions.

## Risks

- Low. Pure removal of a UI surface; heartbeat data and APIs are
untouched. Operators still listing `instance.heartbeats` in
`PAPERCLIP_HIDDEN_SETTINGS` get a warning log and otherwise unchanged
behavior (covered by the registry's unknown-key handling). Bookmarks and
remembered paths to the old page land on the settings root.

## Model Used

Claude (Anthropic), model id `claude-fable-5`, extended thinking,
agentic tool use via Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-27 11:31:43 -07:00

121 lines
6.8 KiB
Markdown

---
title: Environment Variables
summary: Full environment variable reference
---
All environment variables that Paperclip uses for server configuration.
## Server Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `3100` | Server port |
| `PAPERCLIP_BIND` | `loopback` | Reachability preset: `loopback`, `lan`, `tailnet`, or `custom` |
| `PAPERCLIP_BIND_HOST` | (unset) | Required when `PAPERCLIP_BIND=custom` |
| `HOST` | `127.0.0.1` | Legacy host override; prefer `PAPERCLIP_BIND` for new setups |
| `DATABASE_URL` | (embedded) | PostgreSQL connection string |
| `PAPERCLIP_HOME` | `~/.paperclip` | Base directory for all Paperclip data |
| `PAPERCLIP_INSTANCE_ID` | `default` | Instance identifier (for multiple local instances) |
| `PAPERCLIP_DEPLOYMENT_MODE` | `local_trusted` | Runtime mode override |
| `PAPERCLIP_DEPLOYMENT_EXPOSURE` | `private` | Exposure policy when deployment mode is `authenticated` |
| `PAPERCLIP_API_URL` | (auto-derived) | Paperclip API base URL. When set externally (e.g., via Kubernetes ConfigMap, load balancer, or reverse proxy), the server preserves the value instead of deriving it from the listen host and port. Useful for deployments where the public-facing URL differs from the local bind address. |
| `PAPERCLIP_HIDDEN_SETTINGS` | (unset) | Comma-separated settings surfaces to hide from the UI and floor at the API, for operators hosting Paperclip for others (managed cloud, internal shared server). See [Hiding settings surfaces](#hiding-settings-surfaces). |
| `PAPERCLIP_SETTING_DEFAULTS` | (unset) | JSON object replacing the schema default of selected instance settings, for hosting operators. See [Operator setting defaults](#operator-setting-defaults). |
### Hiding settings surfaces
`PAPERCLIP_HIDDEN_SETTINGS` takes keys from the registry in
`packages/shared/src/settings-visibility.ts`:
- Any instance settings page: `instance.profile`, `instance.environments`,
`instance.access`, `instance.experimental`,
`instance.plugins`, `instance.adapters` — removed from navigation and
routing (the General page is the settings root and stays visible). Hiding
`instance.access`, `instance.plugins`, or `instance.adapters` also floors
their management endpoints with `403 settings_operator_managed`; hiding
`instance.experimental` floors every experimental toggle write.
- Any Instance → General section: `instance.general.censorUsernameInLogs`,
`instance.general.keyboardShortcuts`, `instance.general.backupRetention`,
`instance.general.feedbackDataSharingPreference` (each also rejects
value-changing writes via `PATCH /api/instance/settings/general`), plus the
UI-only `instance.general.deploymentStatus` and `instance.general.signOut`.
- Any experimental toggle: `instance.experimental.<flagKey>` (e.g.
`instance.experimental.enableSmokeLab`) — the card disappears and
value-changing writes are rejected.
- Any top-level company settings page: `company.members`, `company.invites`,
`company.secrets`, `company.export`, `company.import` — removed from the
settings sidebar, tab bar, and routing (the company General page is the
settings root and stays visible). These are UI-visibility keys: the
membership, invite, secret, and export APIs stay live for agents and
integrations. `company.import` is the exception — hiding it also floors
every company-import route with `403 settings_operator_managed`. On
cloud-managed instances import is floored unconditionally with
`403 cloud_managed`, independent of this variable.
- A single tab of the Secrets page: `company.secrets.vaults` (Provider
vaults) and `company.secrets.proposals` (Proposals) — the tab disappears
while the rest of the page stays up. UI-visibility only; the secret
provider-config and proposal APIs stay live for agents and integrations.
Unknown keys are logged and ignored, so one list can be rolled across a fleet
of mixed app versions, and retired keys (like `instance.heartbeats`, whose
page was removed) can stay in an operator list without breaking older or
newer releases. With the variable unset nothing is hidden and behavior
is identical to earlier releases. Hiding a toggle does not change its value;
pair hiding with the desired default where it matters (for general settings,
see [Operator setting defaults](#operator-setting-defaults)).
### Operator setting defaults
`PAPERCLIP_SETTING_DEFAULTS` takes a JSON object whose fields come from the
registry in `packages/shared/src/setting-defaults.ts` (currently
`feedbackDataSharingPreference`). The operator value substitutes for the
schema default at read time: any field whose effective value is still the
schema default resolves to the operator value, while an explicit non-default
user choice always wins. The overlay is never persisted, so unsetting the
variable restores stock behavior wherever a user has not chosen otherwise.
A client that writes back the full settings object it read does not persist
the operator value either: writing the operator value over a still-unchosen
field is treated as an echo of the overlay and the field stays unchosen.
Example: `PAPERCLIP_SETTING_DEFAULTS='{"feedbackDataSharingPreference":"allowed"}'`
defaults AI feedback sharing to allowed; pairing it with
`instance.general.feedbackDataSharingPreference` in `PAPERCLIP_HIDDEN_SETTINGS`
also hides the control and floors value-changing writes.
Unknown field names are logged and ignored (mixed-version fleet safe).
Malformed JSON or an invalid value for a known field refuses startup — policy
configuration fails closed.
## Secrets
| Variable | Default | Description |
|----------|---------|-------------|
| `PAPERCLIP_SECRETS_MASTER_KEY` | (from file) | 32-byte encryption key (base64/hex/raw) |
| `PAPERCLIP_SECRETS_MASTER_KEY_FILE` | `~/.paperclip/.../secrets/master.key` | Path to key file |
| `PAPERCLIP_SECRETS_STRICT_MODE` | `false` | Require secret refs for sensitive env vars |
## Agent Runtime (Injected into agent processes)
These are set automatically by the server when invoking agents:
| Variable | Description |
|----------|-------------|
| `PAPERCLIP_AGENT_ID` | Agent's unique ID |
| `PAPERCLIP_COMPANY_ID` | Company ID |
| `PAPERCLIP_API_URL` | Paperclip API base URL (inherits the server-level value; see Server Configuration above) |
| `PAPERCLIP_API_KEY` | Short-lived JWT for API auth |
| `PAPERCLIP_RUN_ID` | Current heartbeat run ID |
| `PAPERCLIP_TASK_ID` | Issue that triggered this wake |
| `PAPERCLIP_WAKE_REASON` | Wake trigger reason |
| `PAPERCLIP_WAKE_COMMENT_ID` | Comment that triggered this wake |
| `PAPERCLIP_APPROVAL_ID` | Resolved approval ID |
| `PAPERCLIP_APPROVAL_STATUS` | Approval decision |
| `PAPERCLIP_LINKED_ISSUE_IDS` | Comma-separated linked issue IDs |
## LLM Provider Keys (for adapters)
| Variable | Description |
|----------|-------------|
| `ANTHROPIC_API_KEY` | Anthropic API key (for Claude Code adapter) |
| `OPENAI_API_KEY` | OpenAI API key (for Codex adapter) |