Upkeeper is a local wrapper around your shell, Git checkout, Codex CLI, and local runtime evidence. It is not a hosted service or a security sandbox by itself.
The practical model is simple: run Upkeeper only in repositories and terminals
where you would be willing to run a local maintenance script plus codex exec.
It can read project files, write local evidence, invoke shell commands through
Codex, and record enough context for later diagnosis.
Upkeeper crosses these local trust boundaries:
- the central Upkeeper checkout that owns
Upkeeper,lib/upkeeper,prompts,docs, and default config - the client repository where
./Upkeeper.shor./Upkeeperis invoked - shell-sourced config files selected by
Upkeeper.conf,--config-file, orUPKEEPER_CONFIG_FILE - the local Codex installation and
codex execbehavior $CODEX_HOME, especiallysessions/, where Codex stores session JSONL data- ignored local evidence such as
Upkeeper.log,runtime/, transcripts, postmortem reports, active locks, manifests, and failure markers
Treat all of those as local operator-controlled state. Do not use Upkeeper to bridge trust between mutually suspicious users, machines, or repositories.
The paired authority docs make the control surface explicit:
docs/authority.mddefines who owns sensitive decisions at each lifecycle point.docs/capability-profiles.mdlists allowed capabilities by actor and mode.docs/control-ledger.mdmaps control claims to enforcement points, tests, and evidence artifacts.docs/policy-decisions.mddefines the JSON schema for local policy decisions that should survive prompt wording changes.docs/decisions/0003-schema-gated-airlocks.mddefines the schema-gated typed-signal boundary pattern for raw evidence that may affect wrapper authority.docs/negative-space-testing.mdlists deterministic "must not happen" validation contracts for safety boundaries.docs/preservation-policy.mddefines evidence temperature, artifact privacy classes, and promotion rules for local and public evidence.docs/source-rights-metadata.mddefines source sensitivity, rights, and reuse metadata for OSINT and citation material before it enters prompts, exports, archives, or public evidence packets.
Upkeeper assumes a trusted local operator, a trusted central wrapper checkout, and a repository that is safe enough for local maintenance automation. It does not assume that backend model output is trustworthy or that local wrapper code is bug-free. The default response to uncertainty is to keep authority in the wrapper, write bounded local evidence, and fail closed before new backend work when machine-health state is not clean. The threat rows below intentionally use the same terms as the tracked issue: malicious model output, confused model output, buggy wrapper, bad config, operator mistake, filesystem weirdness, same-user process access, repo-local secret leakage, public docs leakage, and quota/fallback weirdness.
Canonical threat categories:
- malicious model output
- confused model output
- buggy wrapper
- bad config
- operator mistake
- filesystem weirdness
- same-user process access
- repo-local secret leakage
- public docs leakage
- quota/fallback weirdness
| Threat | Coverage status | Current doctrine |
|---|---|---|
| Malicious model output | Partially covered | The wrapper owns target selection, GitHub effects, status parsing, quota checks, backups, and validation. Backend Codex stays inside the configured sandbox and cannot directly override wrapper safety blocks, but a trusted repo and same-user shell remain required. |
| Confused model output | Partially covered | Parseable markers, selected-target authority, failure custody, and local validation reduce confused output from silently becoming success. Semantic correctness still needs tests, review, or a later fixing cycle. |
| Buggy wrapper | Partially covered | Runtime logs, obligations, breadcrumb custody, focused tests, quick validation, and CI turn wrapper faults into tracked repair work. Some wrapper bugs will still first appear as failed or anomalous runs. |
| Bad config | Partially covered | Config files are documented as trusted shell code, selected config paths are operator-controlled, and local env files are kept machine-local. Upkeeper does not safely execute hostile config. |
| Operator mistake | Partially covered | One-cycle CLI overrides, explicit unsafe knobs, logs, and compatibility docs make intentional operator choices visible. Upkeeper cannot prevent every trusted operator command from doing harm. |
| Filesystem weirdness | Partially covered | Unsafe logs, symlinked targets, ignored/runtime paths, non-private evidence directories, and unsafe session paths are rejected or degraded with evidence. Same-user races and hostile local filesystems are only partially covered. |
| Same-user process access | Partially covered | Private permissions, ignored runtime state, encrypted selected-target backups, and token stripping limit accidental exposure. True isolation from another process running as the same OS user is out of scope without stronger OS confinement or a separate user. |
| Repo-local secret leakage | Partially covered | Secret-bearing files should stay ignored, prompts/logs redact known sensitive paths, and public docs warn against sharing raw evidence. Upkeeper cannot guarantee that project commands or model output never print secrets. |
| Public docs leakage | Partially covered | Public documentation policy, P26 review, docs checks, and release-note discipline reduce private-chat leakage. Operators still need to sanitize examples before publication. |
| Source rights leakage | Partially covered | Source sensitivity and reuse metadata in docs/source-rights-metadata.md separates public, paid-access, license-restricted, PII-bearing, prompt-safe, and export-safe decisions before OSINT or citation material enters prompts or public evidence. Runtime enforcement is still future work. |
| Quota/fallback weirdness | Partially covered | Session parsing, quota guardrails, cooldown markers, fallback limits, and stale-evidence obligations make quota and fallback state visible. Provider state can be stale or incomplete, so degraded or blocked outcomes remain possible. |
Future work should reduce the partially covered rows by replacing broad model-dependent judgment with deterministic local checks, tighter confinement, and smaller authority surfaces.
Degraded mode means Upkeeper can still preserve evidence and explain the next operator-safe action, but must not pretend the run is fully healthy. When a degraded condition affects safety or custody, machine health outranks fresh GitHub issue work.
| Degraded condition | Default behavior | Override policy |
|---|---|---|
| age is missing | Live full-burn paths that require encrypted selected-target backups stop before backend launch and print the bootstrap command. Dry-run and documentation-only paths may continue when they do not need a live encrypted backup. | Install age or choose a profile that does not require encrypted pre-contact backup. Codex cannot override this block. |
| encrypted backup is required but unavailable | Stop before backend launch with evidence that backup protection was not established. | The operator may deliberately change backup requirements for a trusted run, but unsafe plaintext backup requires explicit unsafe knobs. Codex cannot override this block. |
| Landlock/bwrap is unavailable | Required confinement preflights fail closed. Optional hardening layers may be reported as unavailable without claiming stronger isolation than exists. | The operator may use a profile whose documented sandbox requirements fit the machine. Codex cannot override or silently weaken confinement. |
| Lattice is unavailable | If Lattice is required, fail closed. If Lattice is advisory, continue without Lattice and record degraded evidence so custody does not rely on missing ledger state. Advisory lattice.unavailable warnings name a reason class, owner_issue=430, the advisory_lattice_degraded contract, and local_logs_runtime_obligations as replacement evidence. |
The operator may make Lattice required or advisory through documented config. Codex cannot override missing required Lattice custody. |
| validators are unavailable | Do not claim validation proof. Validation phases fail or skip with an explicit local-environment reason, and merge paths must preserve an obligation before continuing. | Fix the local dependency or choose a documented validation scope that does not need it. Codex cannot mark missing validators as passed. |
| dirty baseline exists | Backlog may autoshelve local changes before issue work. Other paths fail or preserve an obligation when clean source state is required. | The operator may clean, commit, autoshelve, or rerun in a documented mode that permits dirt. Codex cannot pretend a dirty baseline is clean. |
| target is unsafe | Reject the target before backend launch when possible, including symlinks, directories, ignored/runtime paths, .git, unreadable files, and binary-like files. |
The operator must choose a safe target or change tracked selection policy deliberately. Codex cannot retarget around the block. |
Overrides are operator controls, not model controls. A valid override should be visible in the command, config, log, obligation, or policy-decision evidence for that run. Unless a setting is a documented persistent config default, override effects expire after one cycle.
| Block reason | Operator override allowed | Codex override | Evidence required | Expiry |
|---|---|---|---|---|
Missing age for encrypted-required live backup |
Yes, by installing age or choosing a non-encrypted-required profile for trusted local work |
Codex cannot override | Bootstrap output, config/profile, or preflight log | One-cycle for CLI changes; config changes persist until edited |
| Encrypted backup required but unavailable | Yes, only with documented backup requirement changes and unsafe plaintext opt-in when plaintext is used | Codex cannot override | Backup mode, encrypted flag, unsafe opt-in, and stop/continue reason | One-cycle for CLI/env overrides unless committed config changes |
| Landlock/bwrap required but unavailable | Yes, by selecting a documented profile that does not require that confinement layer | Codex cannot override | Preflight reason and effective sandbox/confinement profile | One-cycle for CLI/env overrides |
| Lattice required but unavailable | Yes, by changing the requirement to advisory in config for a trusted run | Codex cannot override | Lattice requirement value, failure reason, owner issue or contract, and degraded or fail-closed log | One-cycle for CLI/env overrides |
| Validators unavailable | Only by selecting a narrower documented validation scope; never by reporting success without proof | Codex cannot override | Skipped command, missing dependency, or failing validation evidence | One-cycle for validation-scope overrides |
| Dirty baseline | Yes, by cleaning, committing, autoshelving, or using a mode that documents dirty-state handling | Codex cannot override | Git status or autoshelve branch/commit evidence | One-cycle for autoshelve or mode override |
| Unsafe target | No direct bypass for unsafe paths; choose a safe target or change source/ignore policy in tracked code | Codex cannot override | Rejected path class and selection reason | No bypass |
| Startup anomaly or open obligation | No bypass for normal unattended work; repair, resolve, or preserve the obligation first | Codex cannot override | Obligation id, mapped repair target, and resolution evidence | No bypass |
| Quota guardrail or cooldown stop | Limited burn-mode bypass is allowed when documented and logged; stale evidence must become an obligation | Codex cannot override | Quota bucket, decision, reset/cooldown evidence, and bypass reason | One-cycle for CLI/env bypass |
| External prompt or private issue packet exposure | Yes, only through explicit opt-in flags or env variables from trusted local context | Codex cannot override | Effective exposure flag and redacted private-packet log fields | One-cycle for CLI/env overrides |
Normal startup and dry-run paths can read:
- tracked and non-ignored repository file metadata for selection
- the selected review target and paired prompt files
- Git status, Git object hashes, ignore rules, and file mtimes
Upkeeper.confor the selected config file$CODEX_HOME/sessionsJSONL files for quota and session diagnostics- prior
Upkeeper.loglines for previous-run anomaly detection - runtime state under
runtime/and configured state directories - source-rights metadata for OSINT and citation material when a workflow records whether material is prompt-safe, export-safe, or public-evidence-safe
During a real backend cycle, Codex may read additional repository files while
reviewing the selected target. Upkeeper's preselection narrows the intended
scope, but Codex is still a local agent with the configured sandbox permissions.
Source material labeled prompt-unsafe, credential-bearing,
secret-adjacent, unreviewed PII-bearing, or otherwise restricted by
docs/source-rights-metadata.md should be represented to Codex only through
allowed metadata, hashes, ids, or operator-written summaries.
Upkeeper can write local control-plane evidence:
Upkeeper.logruntime/upkeeper-transcripts/runtime/upkeeper-file-manifest.jsonruntime/startup-anomaly-gates/runtime/unaddressed-tool-failures/runtime/upkeeper-automation-ledger/runtime/upkeeper-obligations/runtime/journals/upkeeper-postmortems/runtime/upkeeper-active.lock- configured wrapper health state under
$CODEX_HOME/upkeeper/ - repo-local
docs/scripts/upkeeper.mdonly when bootstrap is enabled, the path is not ignored, and the guide is missing
During a real backend cycle, Codex can modify repository files according to the
configured Codex sandbox mode and the task it performs. Dry-run mode
(UPKEEPER_DRY_RUN=1) stops before launching real backend work.
After Upkeeper selects a review target and before it appends the selected-target
authority block to the compiled prompt, it creates a pre-contact backup when
UPKEEPER_PRECONTACT_BACKUP_ENABLED=1. The default is required
(UPKEEPER_PRECONTACT_BACKUP_REQUIRED=1) and encrypted-required
(UPKEEPER_PRECONTACT_BACKUP_REQUIRE_ENCRYPTED=1), so backup creation failures
stop the cycle before backend launch with codex_exec_started=0.
The default vault root is outside the repository:
${XDG_STATE_HOME:-$HOME/.local/state}/upkeeper/precontact-vaultUpkeeper does not put that vault path in the compiled prompt, Lattice preselect
evidence, or pre-contact backup log lines. Success logs contain only the
selected target HMAC, backup mode, encrypted flag, backend-protection flag, and
path_redacted=1.
Plain backup mode copies the selected file and a JSON sidecar. It is useful for
quick recovery, but it is not a security boundary: a same-user backend process
that can discover and access the vault through other means may be able to read
or delete plain artifacts. Plain mode therefore requires an explicit unsafe
operator opt-in with both UPKEEPER_PRECONTACT_BACKUP_REQUIRE_ENCRYPTED=0 and
UPKEEPER_PRECONTACT_BACKUP_ALLOW_UNSAFE_PLAINTEXT=1, and Upkeeper rejects the
plaintext path when the selected file contains high-confidence private-key
material. Upkeeper records accepted plain backups as encrypted=false and
protected_from_backend=false.
Age mode encrypts the backup payload to a configured public recipient. Backup creation uses only the public recipient; it must not request, read, log, or source a private age identity. The private identity is needed only for manual restore and must not be committed to config, included in prompts, printed in logs, or placed in an environment visible to backend Codex processes. Without a separate backend confinement layer, encrypted mode protects content at rest but does not make same-user deletion impossible.
The repo-root automation launchers FlameOn and ChimneySweep run the
full-burn profile: Lattice is required, selected-target backup is required,
encrypted backup is required, and CODEX_MODE is pinned to
--sandbox workspace-write. They also set quota stop floors and weekly buffers
to 0, bypass wrapper quota guardrail stops, and bypass persisted quota
cooldown markers, so live launcher runs can spend the selected model bucket down
to the provider floor. Live launcher runs therefore require age and an
UPKEEPER_PRECONTACT_BACKUP_AGE_RECIPIENT before any backend Codex task can
start. Upkeeper now resolves that prerequisite before issue selection on live
apply-stage or normal repair cycles, and records the missing-recipient path as
machine-local operator setup rather than as a target-file bug.
ChimneySweep's default issue workflow is a combined single-issue-fix Upkeeper
invocation. Use --cycle-mode=separate, or an explicit --workflow=..., when
the repair needs the legacy staged workflow: comment, review, then apply
across separate backend instantiations. In that staged mode, the comment and
review stages are tracked-source read-only; the wrapper fingerprints tracked
source before launch and fails the stage if tracked source changes. Those
stages also force backend Codex into a read-only repository sandbox. The
proposed issue comment is carried back in a final-message draft block that the
wrapper extracts only after the source guard passes. GitHub I/O is
wrapper-brokered: the wrapper fetches issue bodies/comments before launch,
backend Codex receives only that issue packet, and the wrapper posts comments
or later issue updates after validation. Backend Codex launches do not inherit
GitHub token variables, use an empty per-run gh config directory, and shadow
direct gh, curl, wget, and hub commands with blocker stubs. The
combined default and the apply stage may edit source, but still do not contact
GitHub directly.
Install age, then bootstrap the public recipient outside the repository:
sudo apt-get update
sudo apt-get install -y age
tools/upkeeper_precontact_bootstrap.shThat command creates or reuses the private age identity under
${XDG_CONFIG_HOME:-$HOME/.config}/age/upkeeper.txt and writes only the public
recipient to ${XDG_CONFIG_HOME:-$HOME/.config}/upkeeper/local.env. The
private identity path should be used for manual restore only and must not be
passed into model prompts or committed configuration.
Landlock, bubblewrap allowlists, root-owned or dedicated-user vaults, fs-verity, and immutable file attributes are separate hardening layers. They may be useful future defenses, but this first local slice deliberately does not install root helpers, sudoers rules, services, ownership tricks, or a custom confinement launcher.
The wrapper itself runs local shell commands needed for startup, validation,
selection, quota parsing, and cleanup. Examples include git, find, jq,
python3, df, ps, sed, awk, and related standard tools.
docs/dependencies.md is the tracked source for the current dependency
decision and install commands; jq remains required until the runtime JSON
bridges are replaced by tested Python-backed helpers.
In non-dry-run mode, Upkeeper launches:
codex exec ...Fallback and postmortem paths may launch additional bounded codex exec calls
unless disabled or stopped by quota/local-environment guardrails.
Upkeeper.conf, configurations/default.conf, files selected with
--config-file=PATH, and the trusted machine-local env file named by
UPKEEPER_LOCAL_ENV_FILE are sourced by Bash. That means they are executable
shell code, not passive key/value data.
Only use config files from trusted locations. Do not point --config-file at a
file from an untrusted repo, issue attachment, shared writable directory, or
downloaded snippet. Keep scheduled-run profiles readable and boring: assignments
such as CODEX_MODEL=..., UPKEEPER_TARGET_FILE=..., and
UPKEEPER_REVIEW_MODULES=... are the intended shape.
CLI flags remain the final one-cycle override surface, but they do not make an untrusted config file safe to source.
Upkeeper defaults CODEX_MODE to Codex workspace-write sandboxing. That is a
useful operational boundary, not a reason to run untrusted code casually.
Review the effective sandbox mode before long loops:
./Upkeeper --help
UPKEEPER_DRY_RUN=1 ./UpkeeperDo not weaken CODEX_MODE unless the repository, machine, credentials, and task
are trusted for that broader access. Upkeeper rejects danger-full-access and
--dangerously-bypass-approvals-and-sandbox, because those modes are
incompatible with the Genie Protocol rule that backend Codex stays inside a
small brokered execution space. If a task needs access outside the repo, prefer
a short manual run with reviewed inputs instead of a long unattended loop.
This repository intentionally does not commit a project .codex/config.toml.
Upkeeper is the wrapper that owns backend launch policy, so sandbox and approval
settings must stay in the wrapper-controlled command line and shell-compatible
Upkeeper profiles rather than a second Codex profile file that can drift.
The supported operator-visible profile surface is:
Upkeeper.conffor central defaultsconfigurations/default.confas the tracked reusable profile template- explicitly selected
--config-file=PATHprofiles from trusted locations - one-cycle CLI overrides such as
--model-override,--target-file, and review-module flags
For non-interactive backend runs, Upkeeper launches codex exec with the
configured model and reasoning effort, pins the working directory, writes the
last backend message to a wrapper-owned file, and supplies the effective Codex
sandbox through CODEX_MODE. Current Codex CLI help exposes --profile,
--profile-v2, --sandbox read-only|workspace-write|danger-full-access, and
--ask-for-approval, but Upkeeper does not rely on a checked-in Codex profile
for those controls. Use Upkeeper configuration instead, because it is validated
by the local wrapper tests, documented in public operator docs, and kept aligned
with the no-real-backend-validation rule.
If the project later adds checked-in Codex profile files, they must be treated
as operator-visible behavior: document the exact intended profile names,
explain how they interact with Upkeeper's command-line enforcement, keep
danger-full-access and approval bypasses out of unattended profiles, and add
local validation that proves the checked-in profile does not weaken the wrapper
contract.
Upkeeper reads recent $CODEX_HOME/sessions/**/*.jsonl files to find quota
snapshots and session diagnostics. It does not need to publish those files.
Session JSONL may contain model names, timestamps, rate-limit state, tool-call
metadata, and fragments of prior Codex interactions. Treat $CODEX_HOME as
local private state. Do not commit it, attach it to public issues, or copy it
into client repositories.
If $CODEX_HOME/sessions is missing, Upkeeper creates it with private
permissions before probing. If it is stale, unreadable, unwritable, symlinked,
not owned by the current user, or otherwise unsafe, Upkeeper should fail or
degrade with local-environment evidence rather than spending more backend work.
An existing session directory owned by the operator but created with weak
inherited permissions is rejected without chmod repair so the operator can fix
the local environment deliberately. Session-store write probes use an
unpredictable private probe directory instead of a predictable marker file.
Upkeeper intentionally records evidence so failures can be diagnosed later. Those records can include:
- absolute local paths
- selected file names and Git status
- command lines and exit summaries
- filtered transcript signal
- final review summaries
- snippets of command output
- quota metadata
- incident classifications and postmortem context
- automation run records and unresolved obligation records
Because project commands may accidentally print secrets, logs and transcripts
must be treated as potentially sensitive. Keep Upkeeper.log and runtime/
ignored unless a repo has a deliberate, reviewed policy for publishing specific
sanitized artifacts.
The preservation policy in docs/preservation-policy.md is the contract for
evidence temperature and artifact privacy classes. Raw runtime evidence defaults
to private-operator; public docs, release notes, issue comments, and PR text
must use public-safe summaries unless a sensitive artifact has been
deliberately sanitized.
Quota/session evidence is now privacy-minimized by default. Normal quota log
lines store hashed session-source and quota-identity fields instead of raw
session paths or limit names, and persisted cooldown markers keep only the
fields needed for enforcement. Full quota diagnostics are reserved for explicit
UPKEEPER_VERBOSE_METADATA=1 runs and chmod-protected local postmortem
artifacts.
Upkeeper refuses unsafe live log paths before its first wrapper log write. A
repo-local Upkeeper.log that is a symlink, non-regular file, hard-linked file,
or owned by another user is rejected before Codex launch; a symlink log parent
directory is rejected as well. Custom CODEX_LOG_FILE paths remain valid live
log sinks for local validation and operator use, but log rotation and sibling
archive pruning stay blocked for custom paths unless explicitly enabled with
CODEX_LOG_FILE_ALLOW_UNSAFE=1 and a trusted Upkeeper rotation marker. This
prevents a contaminated checkout from redirecting wrapper log appends into an
unsafe local file or using archive pruning to delete sibling zip files in an
arbitrary log directory.
Rotation marker refresh also uses no-follow descriptor opens and exclusive
temporary-file creation, so a precreated marker temp symlink cannot redirect
marker writes outside the log directory.
Automatic rotation avoids .git/, ignored paths, .upkeeperignore paths,
runtime evidence, generated outputs, and test trees. Explicit --target-file
pins still reject ignored paths, .upkeeperignore paths, runtime evidence,
.git, directories, symlinks, unreadable files, and binary-like files.
Manifest-backed selection, direct enumeration, and Lattice/max-cover diagnostics
also reject source paths that are symlinks before following, statting, or
sampling them, so a tracked symlink cannot be used to hand Codex an outside-repo
target.
That selection policy is a safety guardrail, not a data-loss prevention system.
If a real backend task runs commands that print or read ignored files, that
output can still enter transcripts. Keep secrets out of repository-adjacent
files when possible, and make sure .env, tokens, private keys, credentials,
and build caches stay ignored.
The recommended client setup is:
ln -s /path/to/central/Upkeeper ./Upkeeper.shWith that shape, the client repo invokes a local ./Upkeeper.sh, but modules,
prompts, default config, and docs resolve from the central Upkeeper checkout.
That central checkout is trusted code. Protect it like any other operational
tooling repository.
Do not replace a symlink with a copied root launcher unless you also keep the
paired lib/upkeeper, prompts, and docs tree in sync. A copied partial
launcher is unsupported and should fail before doing dangerous work.
Running Upkeeper in a client repo means trusting that repo enough to:
- inspect its files and Git metadata
- run local validation or test commands if Codex chooses them
- let Codex propose or apply bounded maintenance edits
- write ignored local Upkeeper evidence
Do not run Upkeeper in a repo from an untrusted source without first reviewing its scripts, hooks, config files, generated files, and test commands. Disable or avoid project-local automation that can run unexpected code before using long loops.
Fallback and postmortem paths are controlled recovery paths, not a way to bypass safety decisions. They inherit the same trust assumptions as the primary run: trusted local repo, trusted shell-sourced config, trusted local Codex installation, configured sandbox mode, private runtime evidence, and ignored logs/transcripts.
Fallback is allowed only when all of these are true:
CODEX_FALLBACK_ENABLED=1- the current process is the primary run, not already inside a fallback chain
- the fallback model, reasoning effort, or mode is different from the primary model configuration
- the configured trigger is enabled, such as primary quota, primary failure, blocked result, or dirty no-backend-task recovery
- local preflight checks pass for the active lock, session store, bubblewrap temp registry, fallback contract, and private evidence paths
- quota guardrails permit the next auxiliary backend call
Fallback is forbidden or skipped when any of these are true:
CODEX_FALLBACK_ENABLED=0- the run is already in a fallback chain
- fallback would use the same model, effort, and mode as the failed primary run
- the local environment is unsafe, including unsafe session, temp, lock, log, or evidence paths
- a default-prompt pre-run quota fallback would only rediscover the same dirty worktree block
- quota or cooldown evidence says the auxiliary model bucket should not be used
A fallback child may mutate files only within the same mode and task boundary as
the parent cycle. If the parent is in a write-capable mode and the selected task
permits repair, fallback may repair tracked files. If the parent is
bug-report-only, source-read-only, review-only, or otherwise constrained, the
fallback child inherits that boundary. Postmortem report paths write private
local evidence by default; hardening work is opt-in with
CODEX_POSTMORTEM_HARDENING_OPT_IN=1.
Fallback is not a dirty-worktree bypass. Dirty worktree state is counted before backend work. A default-prompt pre-run quota fallback is skipped when it would encounter the same dirty-worktree stop. Explicit prompt or target context can be carried into fallback so the child has the same operator-provided task boundary.
Screen fallback is single-shot by default. Set
CODEX_FALLBACK_SCREEN_CONTINUOUS=1, raise
CODEX_FALLBACK_SCREEN_MAX_CHILDREN, and optionally set
CODEX_FALLBACK_SCREEN_MAX_SECONDS to opt into bounded multi-child fallback.
Direct fallback launches at most one child.
Disable all recovery model work with:
CODEX_FALLBACK_ENABLED=0 CODEX_FALLBACK_SCREEN_ENABLED=0 CODEX_POSTMORTEM_ENABLED=0 ./UpkeeperFallback spend is bounded by the exact-model quota preflights, cooldown markers, single-shot defaults, child-count/time limits, and recursive fallback disablement inside fallback children. Postmortem and hardening phases run their own exact-model quota checks and write shell-only reports when they are skipped by quota or local-environment guardrails.
Fallback evidence is separated from primary evidence under private per-cycle
postmortem directories such as runtime/journals/upkeeper-postmortems/<cycle>/.
The fallback contract, screen status files, fallback transcript, incident
context, bug record, and postmortem report are operational evidence and should
remain local unless deliberately sanitized.
Fallback success means the child run and any enabled postmortem sequence finish with the expected machine markers and acceptable exit status. Missing markers, unsafe local evidence, invalid child state, failed postmortem phases, or non-zero child exits remain recovery failures and propagate to the parent cycle.
Upkeeper does not intentionally ask for secrets, but it can record outputs from tools and Codex. Avoid running it where ordinary project commands print secrets.
Before running Upkeeper:
- keep
.env, credentials, private keys, local tokens, and generated secret material ignored - avoid passing secrets in CLI flags or prompt text
- avoid putting secrets in
Upkeeper.confor named config profiles - prefer environment variables managed by the shell or OS credential store
- review transcripts and logs before sharing them outside the machine
If a secret appears in Upkeeper.log, a transcript, a postmortem, or a queued
failure marker, treat that artifact as compromised local evidence. Rotate the
secret before publishing any sanitized excerpt.
Do not commit:
Upkeeper.logruntime/runtime/upkeeper-transcripts/runtime/journals/upkeeper-postmortems/runtime/unaddressed-tool-failures/$CODEX_HOME- Codex session JSONL files
- local copied wrappers from client repos
- credentials, tokens, private keys,
.envfiles, or secret-bearing fixtures - raw transcripts or postmortems unless they have been deliberately sanitized
Commit durable lessons instead: docs, prompt changes, validation fixtures, source fixes, or explicit sanitized excerpts.
Do not run Upkeeper when:
- the repo, config file, or symlink target is untrusted
- the working tree contains secrets that project commands might print
- a branch contains destructive scripts or tests you have not reviewed
- you cannot afford local edits, logs, or transcript creation
- quota state is unknown and you are not using dry-run
- the Codex sandbox mode has been broadened beyond what the task needs
- the repo has legal, privacy, export-control, or customer-data constraints that forbid model-backed review
- the machine is shared with users who should not access the repo or evidence
- you need a human design/product decision before code maintenance can proceed
Use dry-run first when in doubt.
These commands are safe starting points because they do not launch real backend Codex work:
./Upkeeper --help
./Upkeeper --version
UPKEEPER_DRY_RUN=1 ./Upkeeper
tools/validate_upkeeper.sh --deps
tools/validate_upkeeper.sh --quick
tools/check_public_docs.sh --quick
tools/stress_upkeeper_corpus.sh --localFor a new client repo, first verify the symlink and dry-run behavior:
ln -s /path/to/central/Upkeeper ./Upkeeper.sh
./Upkeeper.sh --version
UPKEEPER_DRY_RUN=1 ./Upkeeper.shMove to a live backend loop only after the repo, config, sandbox mode, ignored files, and expected evidence paths are understood.