Entire stores AI session transcripts and metadata in your git repository. This document explains what data is stored, how sensitive content is protected, and how to configure additional safeguards.
When you use Entire with an AI agent (Claude Code, Codex, Gemini CLI, OpenCode, Cursor, Factory AI Droid, Copilot CLI, Pi), session transcripts, user prompts, and checkpoint metadata are committed to your own git repository. They stay out of your working branches' history, but they live in the same repo and travel with it.
Exactly where depends on the checkpoint backend the repo uses:
| Backend | Committed checkpoints land in | Notes |
|---|---|---|
git-refs (default for new repos) |
One ref per checkpoint: refs/entire/checkpoints/<shard>/<id> |
Not a branch. Pushed individually, and fetched individually on demand |
git-branch (legacy) |
Subtrees of one long-lived branch, entire/checkpoints/v1 |
A branch, so it shows up in branch listings, CI branch triggers, and platform previews |
Which one a repo is on is recorded as checkpoints.primary.type in .entire/settings.json (or settings.local.json); an absent checkpoints block means git-branch. The redaction described in this document applies identically to both — the pipeline is shared, and only the destination differs. Where the distinction matters below, it is called out.
Entire also creates temporary local shadow branches (e.g. entire/<commit>-<worktree>) as working storage during a session, on both backends. Metadata written there — transcripts, prompts, incremental checkpoint data, subagent transcripts — goes through the same redaction pipeline as a committed checkpoint. Code-file snapshots, however, are written as raw blobs of your working tree without redaction, so any hardcoded secrets in your source code would appear unredacted on the shadow branch. Gitignored files (e.g., .env) are filtered out of these snapshots as a partial defense. Shadow branches are not pushed by Entire; do not push them manually, because unredacted source content would be visible on the remote. They are cleaned up when session data is condensed into a checkpoint at commit time.
Anyone with access to your repository can read committed checkpoint data: the full prompt/response history and session metadata. Note that transcripts capture all tool interactions — including file contents, MCP server calls, and other data exchanged during the session. Per-checkpoint refs are less visible than a branch, but they are not less accessible: a git fetch of refs/entire/checkpoints/* reads them just as well.
If your repository is public, this data is visible to the entire internet.
Entire automatically scans transcript and metadata content before writing it to a git object. Five always-on secret detection methods plus a configurable scanner layer (pattern matching, method 2 below) run during condensation, plus a conditional seventh pass for user-defined secret rules (see Customizing redaction below), an opt-in eighth pass for PII (see Optional PII redaction below), and an opt-in ninth pass that shells out to the OpenAI Privacy Filter model (see Optional OpenAI Privacy Filter below):
- Entropy scoring — Identifies high-entropy strings (Shannon entropy > 4.5) that look like randomly generated secrets, even if they don't match a known pattern.
- Pattern matching — Runs one or both configurable scanner engines against known secret formats: Betterleaks (default on) and/or goredact (default off). See Choosing secret-scanner engines below.
- Provider token prefixes — Deterministically redacts known secret-key prefixes (e.g. Supabase
sb_secret_,sbp_) regardless of entropy or surrounding context. - Credentialed URI detection — Redacts URLs with embedded passwords, such as
scheme://user:password@host. - Database connection-string detection — Redacts JDBC, Postgres keyword DSN, SQL Server, and ODBC-style connection strings containing passwords.
- Bounded credential value detection — Redacts password-like config values such as
DB_PASSWORD=...andPGPASSWORD=...while preserving the surrounding key.
Detected secrets are replaced with REDACTED before the data is ever written to a git object. Of the six secret-detection passes above, the scanner layer (pass 2) is configurable — see Choosing secret-scanner engines below — while the other five are always on and cannot be disabled. User-defined rules (inline custom_redactions and rule packs) add a seventh secret-detection pass that only runs when configured.
Every layer described above — all nine passes, including the opt-in PII and OpenAI Privacy Filter layers — reads transcript text. There is no OCR pass, no vision-model PII or secret scan, and no gate that holds an image back until someone reviews it. Nothing in Entire ever reads what an image depicts.
What that means for an image you paste is not uniform across agents, because it depends on how the agent writes the image into its transcript. There are three outcomes, and only the first is an exposure:
| Agent | Default | With redaction.externalize_images on |
|---|---|---|
| Claude Code | Stored unredacted, inline in the transcript as base64. The text scanner skips it (the type: image / type: base64 skip rule below) rather than scanning it. |
Stored unredacted as a raw binary blob under the checkpoint's assets/ folder. |
| Codex | Destroyed. Codex writes images as data: URIs inside image_url and tool-output strings, which the skip rule does not match, so the entropy layer treats the base64 as a secret and replaces it. The stored transcript keeps the surrounding message; the image is gone. |
Stored unredacted under assets/ (externalization runs before redaction, which is what preserves it). |
| Cursor | Not stored in the repository at all. Cursor keeps images in its own per-session SQLite store, never in the transcript Entire reads. | Stored unredacted under assets/, captured from that store. |
| Gemini CLI, OpenCode, Copilot CLI, Factory Droid, Pi | Depends on the agent's own transcript shape; Entire has no image handling for these. Assume the Claude Code row unless you have checked. | Unchanged — the setting only affects the three agents above. |
Do not treat the Codex row as a protection. It is a side effect of a skip rule not matching a shape, not a deliberate safeguard: it destroys data you may want, it does not apply to the assets/ path, and a change to either the rule or Codex's format would flip it to the exposure case without notice.
Where an image is stored, it is byte-for-byte what you pasted — completely unredacted — because byte-level regex and entropy redaction cannot inspect binary image data without corrupting it, so Entire does not attempt it. That is a deliberate design choice, not a bug.
Two things about when those bytes reach git, both of which narrow the window you have to catch a mistake:
- A checkpoint's copy lands on
entire/checkpoints/v1(or the equivalent per-checkpoint ref on thegit-refsbackend). Like all checkpoint data it is written locally and pushed separately, so it reaches your remote only when checkpoint data is pushed — see the Review before pushing bullet under Recommendations. - A shadow-branch copy is written earlier, at the end of the agent turn, before you commit anything and regardless of the setting above. The shadow write does not externalize images, so for Claude Code the base64 is committed into your local git objects at turn end. Shadow branches are local-only and are never pushed (see Where data is stored above), but
entire checkpoint listwill not show this copy — it exists before any checkpoint does.
If you would not commit an image to your repository unredacted, do not paste it into an agent conversation. This applies equally to a private repository — anyone with read access to checkpoint data can see it — and especially to a public one.
Pattern matching (layer 2 above) is served by two independent scanner engines, each of which can be turned on or off:
- Betterleaks — a broad rule-set auditor with several hundred built-in detectors for known secret formats (cloud providers, VCS platforms, payment processors, private key blocks, generic credentials, and more). Default: on.
- goredact — a streaming, validator-based scanner that checks a smaller set of provider/contextual token shapes against structural validators (e.g. checksum or length checks) rather than pure regex. Default: off.
Configure them under redaction.betterleaks / redaction.goredact in .entire/settings.json:
"redaction": {
"betterleaks": { "enabled": true },
"goredact": { "enabled": false }
}Omitting either key, or the key's enabled field, keeps that engine at its default. All other redaction layers — entropy scoring, provider token prefixes, credentialed URI detection, connection-string detection, custom rules, bounded credential key/value detection, and PII — are unaffected by these two toggles; they keep running exactly as described elsewhere in this document regardless of which scanner engine(s) are selected.
Fail-closed rules:
- At least one scanner engine must be enabled. Setting both
betterleaks.enabledandgoredact.enabledtofalseis a settings error: Entire refuses to load the (merged) settings rather than run condensation with no pattern-matching coverage at all. - Scanner selection is honored only from the committed
.entire/settings.json. Abetterleaksorgoredactkey present in.entire/settings.local.jsonis ignored, and Entire logs a warning naming the ignored key. This is deliberate: unlike mostsettings.local.jsonoverrides, which are personal and don't affect anyone else, the scanner selection changes what gets redacted into checkpoints that every reader of the repository's history will see — so it has to be a team-visible, committed decision, not a per-developer one. - Disabling Betterleaks narrows layer-2 coverage to whatever engine(s) remain enabled. The first time a hook or CLI command runs with Betterleaks disabled, Entire logs a one-time notice to that effect (printed on the terminal when stderr is a TTY; suppressed on subsequent runs via a marker file under
.entire/tmp/).
Runtime degradation: if goredact is the only enabled scanner and it fails at runtime (a scan error, not a missing finding), Entire treats that as scanner degradation and fails the transcript write rather than persisting content that only received partial pattern-matching coverage. This is a deliberate fail-closed choice: with Betterleaks also enabled, a goredact failure degrades gracefully to Betterleaks-only coverage for that write; with Betterleaks disabled, there is no fallback engine left, so the write itself must fail instead of shipping under-scanned content.
The coverage trade-off, honestly stated: Betterleaks' several-hundred-rule set covers a long tail of structured, often low-entropy token formats that a smaller rule set would miss; goredact covers roughly 67 provider/contextual token shapes but checks each one with a dedicated validator, trading breadth for precision. Neither engine is a strict superset of the other — running both (the default plus opting into goredact) gives the widest coverage.
PII redaction is a separate, opt-in layer that runs in addition to the always-on secret detection. Disabled by default. Configured under redaction.pii in .entire/settings.json (team-shared) or .entire/settings.local.json (personal, gitignored).
Built-in categories (when enabled is true):
| Category | Default | Replacement token |
|---|---|---|
email |
on | [REDACTED_EMAIL] |
phone (North American / NANP formats) |
on | [REDACTED_PHONE] |
address (US street addresses) |
off (more false-positive prone) | [REDACTED_ADDRESS] |
Common bot/CI email addresses are not redacted (noreply@*, actions@*, *@users.noreply.github.com, *@noreply.github.com).
Teams can add their own regex patterns via custom_patterns. Each key is a label (uppercased in the replacement token), each value is a regex string. Example: {"employee_id": "EMP-\\d{6}"} produces [REDACTED_EMPLOYEE_ID].
{
"redaction": {
"pii": {
"enabled": true,
"email": true,
"phone": true,
"address": false,
"custom_patterns": {
"employee_id": "EMP-\\d{6}"
}
}
}
}If a custom pattern itself reveals sensitive structure (e.g. an internal ID format), put it in .entire/settings.local.json (gitignored) instead of .entire/settings.json.
A separate, opt-in layer that shells out to the OpenAI Privacy Filter (opf) — a 1.5B-parameter token-classification model that finds names, emails, phone numbers, addresses, dates, URLs, account numbers, and secrets that pure regex can miss. Disabled by default. Runs in addition to the eight built-in layers, only at push time — never per-turn and never at commit time. Local commits stay on the fast 8-layer pipeline so per-commit latency is unchanged; OPF only re-redacts checkpoints right before they leave the machine via git push.
Prerequisites:
pip install opfVerify opf --help works; the CLI defaults to resolving the binary via $PATH. If you need a specific path, set command in .entire/settings.local.json — it is deliberately not honored from the committed .entire/settings.json. See Why command is local-only.
Enable in .entire/settings.json:
{
"redaction": {
"openai_privacy_filter": {
"enabled": true,
"categories": {
"private_person": true
}
}
}
}Available categories (set to true to enable, false or omit to skip):
| Category | Replacement token | Notes |
|---|---|---|
private_person |
[REDACTED_PERSON] |
Person names |
private_email |
[REDACTED_EMAIL] |
Email addresses |
private_phone |
[REDACTED_PHONE] |
Phone numbers |
private_address |
[REDACTED_ADDRESS] |
Street addresses |
private_url |
[REDACTED_URL] |
URLs that may identify a person/account |
private_date |
[REDACTED_DATE] |
Dates (DOB, etc.) |
account_number |
[REDACTED_ACCOUNT_NUMBER] |
Account / card / SSN-shaped numbers |
secret |
REDACTED |
Generic credential-shaped values |
Unknown category names are rejected at settings load time so typos surface immediately instead of silently disabling a category.
The filter needs at least one enabled category to run. This is enforced at push time, not settings load: with enabled: true and no effective category (categories omitted, empty, or all-false) the model scan cannot run. Rather than tagging commits as OPF-applied without a scan, it fails closed the same way a runtime failure does: git-branch aborts the push, git-refs withholds the checkpoint refs and lets your push through. Enable a category, set enabled: false, or pass ENTIRE_OPF=no on a push to skip the filter for that push only.
Full settings reference:
{
"redaction": {
"openai_privacy_filter": {
"enabled": true,
"categories": {
"private_person": true,
"private_email": true,
"private_phone": true,
"private_address": false,
"private_url": false,
"private_date": false,
"account_number": false,
"secret": false
},
"timeout_seconds": 30
}
}
}command— path or PATH-resolvable name of theopfbinary. Defaults toopf. Only read from.entire/settings.local.json, and only when that file is untracked; see Whycommandis local-only.timeout_seconds— per-invocation timeout. Defaults to30.prompt_default—"ask"(default),"never", or"always". Controls whether the pre-push hook surfaces an interactive prompt before running OPF.ENTIRE_OPF=yesorENTIRE_OPF=noon a singlegit pushinvocation overrides this for that push only.
command becomes argv[0] of a process Entire executes during git push, so whoever controls that string controls what runs on the developer's machine. .entire/settings.json is version-controlled, which would let an ordinary pull request pair a command with a payload committed alongside it — and a JSON settings diff does not read as executable to a reviewer. The pre-push prompt is no defense either: it never names the command, prompt_default: "always" skips it, and non-TTY pushes (CI, agent-driven) auto-run.
Entire therefore honors command only when it is genuinely developer-owned:
- it must come from
.entire/settings.local.json, not.entire/settings.json - that file must be untracked — absent from both the git index and
HEAD
The second check matters because the filename alone proves nothing: .gitignore does not apply to a path that is already tracked, so git add -f .entire/settings.local.json commits it and a fresh clone materializes it with the committed content.
This is enforced for the whole file, not just this setting: a tracked .entire/settings.local.json is ignored in its entirety, because it is not local to your clone — it arrives with the repository and would override project settings for everyone. Entire warns on stderr and tells you to run git rm --cached .entire/settings.local.json. The load still succeeds using project settings, so a committed file cannot brick the repository.
The two checks also differ in depth. The layer check looks at the git index; the command check also looks at HEAD. A pull request that commits the file puts it in the index of every clone that checks the branch out, so the index is what catches a delivered attack — and checkout cannot produce a file that is absent from the index, so "committed, then git rm --cached" is a state you created locally, not one that arrived with the repository. Reading HEAD is the expensive half, so it is reserved for the setting that gets executed.
The two checks fail in opposite directions on purpose. If the repository cannot be read at all, the local layer is still applied — losing every local preference over an unreadable repo is worse than the risk. The executed command is dropped in that case, because being wrong there means running someone else's binary. With no repository at all, nothing can have arrived by cloning, so the file is treated as yours.
When a command fails these checks it is ignored with a warning in .entire/logs/entire.log and OPF falls back to resolving opf on $PATH. If that binary is missing, the pre-push rewrite fails closed rather than pushing content you believed OPF had scanned. Everything else in the OPF block (enabled, categories, timeout_seconds, prompt_default) is ordinary configuration and still works from the shared project file.
The interactive prompt offers three options and reacts to Ctrl-C for cancellation:
Run OpenAI Privacy Filter on these checkpoints?
Adds ~30s but redacts names/PII the regex layers can't catch.
Ctrl-C to cancel the push.
▸ Yes — run OPF this push
No — skip OPF, push as-is
Always — run OPF on every push from now on
- Yes runs OPF for this push only.
- No skips OPF for this push only; the 8-layer-redacted content reaches the remote.
- Always runs OPF this push AND writes
prompt_default: "always"to.entire/settings.local.jsonso future pushes don't ask. - Ctrl-C cancels OPF. What that costs depends on the backend: on
git-branchyourgit pushaborts and exits non-zero; ongit-refsyour push completes and the checkpoint refs stay queued for a later push. Either way nothing under-redacted reaches the remote.
Non-interactive contexts (CI, scripted pipes with no TTY) skip the prompt and run OPF automatically when enabled, printing → OpenAI Privacy Filter: scanning checkpoints before push (may take ~30s)… to stderr so the wait isn't silent. Progress and completion are reported as → OpenAI Privacy Filter: scanning checkpoints… and ✓ OpenAI Privacy Filter: done (12.4s, 37 blobs). Set ENTIRE_OPF=no to skip OPF in those contexts without disabling the feature globally.
CI consideration: if you've enabled OPF locally and your CI runs git push (e.g. an agent-driven workflow), the CI push will attempt to run OPF too. If the opf binary isn't installed in CI, the failure is fail-closed rather than silently shipping under-redacted content — by design, since "I enabled OPF" should mean "no content leaves my machines without OPF." On git-branch that aborts the CI push; on git-refs the push succeeds but the checkpoints don't ship, which means they can accumulate unpushed until someone notices. The remedies are (a) install opf in CI, (b) set ENTIRE_OPF=no for CI pushes, or (c) set prompt_default: "never" if you only want OPF on interactive pushes.
OPF failures at push time are fail-closed: if OPF is not on PATH, fails to start, or times out during the pre-push rewrite, the per-process circuit breaker trips and no under-redacted content reaches the remote. The intent is that "the user enabled OPF" means "I do not want unredacted content leaving this machine" — falling back to 8-layer silently on the push path would violate that contract. Fix the install or set ENTIRE_OPF=no for a one-off push.
How that is enforced depends on the checkpoint backend, because they have different escape hatches:
- git-branch: the rewrite aborts the push with
OPF runtime failed during pre-push rewrite (command=…); aborting push so regex-only content isn't tagged as OPF-applied. Yourgit pushexits non-zero. The checkpoint branch travels with that push, so refusing the push is the only way to withhold it. - git-refs: checkpoint refs are pushed separately from your branch and stay queued when they are not flushed, so the failure withholds the checkpoint push and lets your own
git pushsucceed. Nothing under-redacted ships either way. This is not silent: a warning names the failure and states that checkpoint refs stayed queued for the next push.
(The circuit breaker is per-process, so a broken install costs one warning instead of one timeout per blob — but the push still aborts.)
Cost note: each shell-out loads the OPF model (~1.5B parameters on CPU). The pre-push rewrite batches every redactable leaf across every unpushed commit — v1 commits on git-branch, every unpushed commit on every queued ref on git-refs — into a single inference pass, so a typical real-world push pays the model-load cost once (~6s) plus inference (~5s per 100KB of leaf content) — not multiplied by the number of commits or blobs. A 3-commit push with ~250KB of total prose content runs in ~12–15s, not the ~50–100s a per-blob flow would take. Per-commit latency is unaffected because OPF doesn't run at commit time.
OPF execution lives in the pre-push hook. The flow:
- Post-commit writes the checkpoint with 8-layer-only redaction to local git objects — per-checkpoint refs on
git-refs, theentire/checkpoints/v1branch ongit-branch. Fast, predictable, no OPF cost on the hot path. - Pre-push (
git push): if OPF is enabled, the hook re-reads each not-yet-OPF'd commit, runs the OpenAI Privacy Filter over its blobs to add the categories the regex layers don't catch (person names, addresses, etc.), and builds new commits carrying anEntire-OPF-Applied: truetrailer. Each backend then points its own local ref at the new tip with a compare-and-swap, and the (now 9-layer-redacted) commits are what get pushed. - The original 8-layer-only commits become unreachable in the local git object database and eventually get swept by
git gc.
The two backends differ in how they find the commits to rewrite:
git-refs |
git-branch |
|
|---|---|---|
| What it walks | Every ref in the push-discovery queue | The entire/checkpoints/v1 commit chain |
| Where it stops | The first ancestor already carrying Entire-OPF-Applied: true — the trailer is the watermark |
The remote's v1 tip, fetched live (not a stale tracking ref) |
| Already-OPF'd commits | Left byte-identical; they are the boundary | Re-parented onto the new chain, but not re-redacted |
| Ref update | Each queued ref rewritten in place, CAS'd individually | One CAS on the local v1 ref |
In steady state the git-refs walk stops at the last commit the previous push OPF'd. That is usually the tip's parent, but not always: each write to a checkpoint advances its ref by one commit, so a turn that creates a checkpoint and then backfills its transcript and summary leaves a three-commit chain to rewrite.
Note that the local ref move is not a fast-forward on either backend — the rewritten chain replaces the old commits rather than descending from them, which is exactly why step 3 leaves them unreachable. The push is still fast-forward-only and never forced, because the new chain is parented on a commit the remote already has.
This means:
- The remote only ever sees 9-layer-redacted content when OPF is enabled.
- Local-only commits are 8-layer-redacted until the moment you push. If you never push, OPF never runs.
- Re-running pre-push is idempotent — commits already carrying the trailer are never re-redacted.
The rewrite refuses to proceed in a divergent or oversized state rather than silently rebasing or shipping unscanned content. As always, git-branch enforces this by aborting your push and git-refs by withholding the checkpoint refs.
Divergence. On git-branch, if local entire/checkpoints/v1 has commits that aren't ancestors of the remote's v1, the hook exits with a entire/checkpoints/v1 has diverged from remote error. Fetch the remote and either reset local v1 to the remote tip or resolve manually before pushing.
git-refs has no divergence pre-check, and doesn't need one: each checkpoint is its own ref, so divergence shows up at push time as a non-fast-forward rejection for that one ref. Recovery fetches the remote ref and replays the local-only commits on top, then retries — still without forcing, so the remote commit is preserved as an ancestor. A ref that can't be replayed stays queued.
Un-OPF'd commit cap: 100 by default. Override per push:
set -x ENTIRE_OPF_BOOTSTRAP_LIMIT 500; git push
# or fully unbounded:
set -x ENTIRE_OPF_BOOTSTRAP_LIMIT unlimited; git pushThe two backends trip this differently. On git-branch it applies only on bootstrap — the first push, when the remote has no v1 yet — counted across all unpushed commits. On git-refs it applies on every push, counted per queued ref over that ref's un-trailered ancestry. In practice the git-refs trigger is "OPF was enabled late" or "checkpoints were just migrated from the branch", not "first push".
Batch cap: 2 MiB of cumulative prose-leaf content by default (≈110s of inference), on both backends. An OPF would run inference on … error means you've hit it:
set -x ENTIRE_OPF_BATCH_LIMIT 10485760; git push # 10 MiB
# or fully unbounded:
set -x ENTIRE_OPF_BATCH_LIMIT unlimited; git pushRaw-byte cap: 200 MiB of blob content buffered in memory, on both backends. It has no env var of its own — it is derived as 100× the batch cap, so raising ENTIRE_OPF_BATCH_LIMIT raises it too. It is checked incrementally as blobs load, so on a pathological push (one commit carrying a huge pasted transcript) this is the cap that fires first.
The three caps protect different failure modes: the commit cap stops "100 throwaway commits", the batch cap stops "one commit with 50 MB of prose", and the raw-byte cap stops the loader exhausting memory before either of the others can be evaluated. On git-refs the two byte caps are cumulative across the whole flush (all queued refs together) while the commit cap is per ref.
Concurrent push from another worktree: both backends compare-and-swap the local ref. If another process moved it while OPF was running, git-branch exits with entire/checkpoints/v1 moved during OPF rewrite …; re-run 'git push' (no fetch needed; the move was local) and aborts. On git-refs the affected ref simply stays queued and the next push picks it up. Note that the git-refs rewrite rebuilds every commit before touching any ref, but the ref updates themselves are not atomic across refs: a conflict partway through leaves the earlier refs already rewritten. They stay queued and push OPF-applied next time, so this is safe — just not "nothing moved".
Several places retain content that OPF didn't redact, with different lifetimes. Understanding them matters if your threat model goes beyond "what reaches the remote":
| Location | Redaction level | Lifetime | Reaches remote? |
|---|---|---|---|
.entire/metadata/<session>/full.jsonl |
None — sanitized, not redacted | Until the session's data is cleaned up (entire clean) |
No |
The agent's own transcript (e.g. ~/.claude/projects/…) |
None — raw | Owned and managed by the agent | No |
Shadow branch entire/<commit>-<worktree> |
8-layer | Auto-deleted after the next successful push (only when its session has ended cleanly) | No |
| Unreachable git objects after the pre-push rewrite | 8-layer | Until git gc --prune (default gc.pruneExpire is 2 weeks) |
No |
Local refs/entire/checkpoints/* (git-refs) |
8-layer before push, 9-layer after the rewrite | Kept indefinitely — these refs are never deleted after pushing | Yes, once pushed |
Local entire/checkpoints/v1 (git-branch) |
8-layer before push, 9-layer after the rewrite | Until you delete the branch | Yes, once pushed |
Reflog of entire/checkpoints/v1 (git-branch only) |
8-layer tips | Default gc.reflogExpire is 90 days |
No |
A configured git-branch mirror |
8-layer, indefinitely | Until you delete the branch | No — mirrors aren't pushed at pre-push |
A leftover entire/checkpoints/v1 after migrating to git-refs |
8-layer | Until you delete the branch | No |
| The remote's copy of the pushed refs/branch | 9-layer (after OPF rewrite) | Until you delete them on the remote | Yes |
Two notes on the git-refs rows:
- There is no reflog concern for
refs/entire/checkpoints/*. Git'score.logAllRefUpdatesdefault only auto-logsrefs/heads/*,refs/remotes/*,refs/notes/*, andHEAD, and Entire's checkpoint-ref writes don't append reflog entries themselves. The reflog row above is genuinely branch-only (unless you've setlogAllRefUpdates=always). - The push queue holds no content.
entire-checkpoint-push-queue.jsonlin the git common dir stores one ref name per line, mode0600. Nothing in it is redactable.
Two git-branch-shaped leftovers are easy to miss once a repo has moved on:
- A
git-branchmirror never gets OPF'd. Mirrors receive best-effort write fan-out only and never ref-level mutations, so a mirror branch keeps 8-layer content indefinitely. It isn't pushed at pre-push, so it doesn't reach the remote — but it is local content, and it has arefs/heads/reflog. - Migration doesn't delete the old branch.
entire doctor migrate-checkpointsimports fromentire/checkpoints/v1and leaves it in place, 8-layer, along with its reflog. Migrated refs also carry no OPF trailer, so the first push after a migration re-OPFs every migrated ref — one commit per ref keeps the commit cap happy, but the 2 MiB batch cap is the realistic trip point.
.entire/metadata/<session>/full.jsonl is Entire's own local working copy of the transcript, written mode 0600. It is sanitized (agent state that cannot be replayed out of a checkpoint is stripped) but not redacted — redaction happens on the way into a git object, not on this file. It is the input the shadow-branch walk and condensation read from.
The agent's own transcript is never modified at all. Entire reads from it and leaves it alone, because the agent is writing to it continuously and editing under the agent's feet would corrupt the session.
To aggressively scrub the unreachable git objects from the pre-push rewrite (instead of waiting for the 2-week GC window):
# git-refs
git reflog expire --expire-unreachable=now --all
git gc --prune=now
# git-branch
git reflog expire --expire-unreachable=now refs/heads/entire/checkpoints/v1
git gc --prune=nowThis is I/O-heavy on large repositories; it's not run automatically. If you want it as part of your push workflow, wrap git push in a script that invokes it after a successful push.
After enabling OPF, run an agent turn whose prompt contains a name — e.g. "Create notes.txt with: Alice Johnson reviewed the proposal." — then commit (which stays on the fast 8-layer pipeline) and push, which is when OPF runs.
git commit -m "demo"
git push # → "OpenAI Privacy Filter: scanning checkpoints…"Then confirm the redaction landed and the trailer is present. Note that git log --oneline cannot show a trailer — it prints only the subject line — so use a format that includes the body:
# git-refs: which checkpoint refs exist, and which are OPF-applied
git for-each-ref --sort=-committerdate \
--format='%(refname) %(trailers:key=Entire-OPF-Applied)' refs/entire/checkpoints/
# git-branch: the trailer on the latest v1 commit
git log -1 --format='%H%n%B' entire/checkpoints/v1
# either backend: confirm the content itself was redacted
entire checkpoint list
entire checkpoint explain <checkpoint-id> | grep -i 'REDACTED_PERSON'If [REDACTED_PERSON] appears in the prompt or transcript section and the checkpoint's commit carries Entire-OPF-Applied: true, OPF is active.
On git-refs you can also confirm nothing was withheld — an absent or empty queue file means everything flushed:
cat "$(git rev-parse --git-common-dir)/entire-checkpoint-push-queue.jsonl"If your AI sessions will touch sensitive data:
-
Use a private repository. This is the simplest and most complete protection. Committed checkpoints are then only visible to collaborators.
-
Avoid passing sensitive files to your agent. Content that never enters the agent conversation never appears in transcripts.
-
Never paste a screenshot or image containing secrets or personal data. Nothing in Entire reads what an image depicts, and on most agents the image is stored unredacted — see What Entire does NOT understand: pasted images and screenshots.
-
Review before pushing. Checkpoints are written locally at commit time and pushed separately, so there is always a window to inspect them:
# git-refs: list local checkpoint refs, then read one git for-each-ref refs/entire/checkpoints entire checkpoint list entire checkpoint explain <id> # git-branch: inspect the branch directly git log --oneline entire/checkpoints/v1
-
Know your push destination. Checkpoint data syncs to exactly one elected remote;
entire statusnames it and reports how many checkpoints are still unpushed.
Secret detection as a whole is always on, though the pattern-matching scanner layer within it is configurable per Choosing secret-scanner engines. Betterleaks pattern matching covers cloud providers (AWS, GCP, Azure), version control platforms (GitHub, GitLab, Bitbucket), payment processors (Stripe, Square), communication tools (Slack, Discord, Twilio), private key blocks (RSA, DSA, EC, PGP, OpenSSH), and generic credentials (bearer tokens, basic auth, JWTs). Dedicated credentialed URI detection covers URLs that embed passwords. Additional database connection-string detection covers DB DSNs and query-parameter passwords not reliably covered by generic secret rules. Entropy scoring catches secrets that don't match any known pattern.
All detected secrets are replaced with REDACTED. PII matches are replaced with category-tagged tokens like [REDACTED_EMAIL] (see Optional PII redaction).
To reduce over-redaction, Entire preserves structural transcript fields such as IDs and paths, leaves placeholder values alone, and redacts only credential values for bounded key/value forms. Placeholders are detected by exact match (e.g. changeme, example, placeholder, your_password, your_secret, prior REDACTED/[REDACTED]/<REDACTED> markers) or by shape: shell expansions like ${DB_PASSWORD}, bracketed names like <password> or <your-db-password>, and mask runs of three or more */x/./- (so ***, xxxx, ...., ---- all match). When a connection string contains a real password, it is redacted as a unit because partial fragments can still expose sensitive material; connection strings whose passwords are placeholders are left intact.
The built-in detectors handle well-known secret formats. For anything else you don't want stored in transcripts — internal credential shapes the bundled scanners don't know about, project codenames, or specific words and phrases you'd rather keep out of session archives — Entire offers two extension surfaces. Both run plain regex matching against transcript content, so the rules can target any string pattern, not just credentials. Both feed the same engine and run as their own layer between connection-string detection and bounded credential KV detection.
Add a label → regex map under redaction.custom_redactions in .entire/settings.json:
{
"redaction": {
"custom_redactions": {
"acme_token": "ACME_TOKEN_[A-Za-z0-9]{20,}",
"internal_id": "INTERNAL_[a-z]{6}_[0-9]{4}"
}
}
}- The label is for diagnostics only; matches are replaced with the bare
REDACTEDtoken (matching the built-in secret layers, not the[REDACTED_<LABEL>]token used for PII). - Regexes follow Go's RE2 syntax. No lookarounds, no backreferences.
- A failed compile is logged once at startup and the rule is skipped — it will never crash the redactor.
- Override in
.entire/settings.local.jsonfor personal additions; entries merge per-key (override replaces the same key, leaves other keys intact).
Drop a YAML or JSON file into .entire/redactors/:
# .entire/redactors/acme-internal.yaml
name: acme-internal # MUST match the filename stem
version: 1.0.0
description: Internal ACME service tokens
rules:
- id: acme-token
description: Long-lived ACME service tokens
regex: 'ACME_TOKEN_[A-Za-z0-9]{20,}'
samples:
- { input: "key=ACME_TOKEN_abc123def456ghi789jkl", redacted: true }
- { input: "ACME_TOKEN_short", redacted: false }
- id: acme-session
regex: 'asess_[a-f0-9]{32}'Equivalent JSON form:
{
"name": "acme-internal",
"version": "1.0.0",
"rules": [
{
"id": "acme-token",
"regex": "ACME_TOKEN_[A-Za-z0-9]{20,}",
"samples": [
{ "input": "key=ACME_TOKEN_abc123def456ghi789jkl", "redacted": true },
{ "input": "ACME_TOKEN_short", "redacted": false }
]
}
]
}Required fields: name (must equal the filename stem — acme-internal.yaml → acme-internal), version (any string; semver recommended), and rules[] (at least one entry, each with id and regex).
Optional fields: description (pack-level and rule-level), and rules[].samples[] (see "Self-tests" below).
A pack does not have to target credentials. The same shape works for any string pattern you don't want stored in transcripts — for example, a project codename or a small word list:
# .entire/redactors/local/private-words.yaml
name: private-words
version: 1.0.0
description: Project codenames and personal words to keep out of transcripts
rules:
- id: codename-falcon
description: Internal project codename
regex: '(?i)\bproject[- ]?falcon\b'
samples:
- { input: "rolling out Project Falcon next week", redacted: true }
- { input: "the falcon flew over", redacted: false }
- id: personal-words
description: Words I'd rather not see archived
regex: '(?i)\b(word_one|word_two)\b'Putting personal lists under .entire/redactors/local/ keeps them out of team commits (see "Distribution" below).
Each rule may declare an array of {input, redacted} pairs. On the next process startup after editing the pack, Entire runs each sample and emits a slog.Warn for any mismatch:
WARN redactor pack sample mismatch pack=.entire/redactors/acme-internal.yaml
rule=acme-token sample_index=0 sample_length=42 expected=true got=false
A failing sample never disables the rule — sample validation is informational. Use it to catch typos and false positives before they ship.
- Within a team: commit
.entire/settings.jsonand/or.entire/redactors/*to your repo. Teammates pull and the rules apply. - Across teams: copy the pack file or share a link to a gist; recipients drop the file into their
.entire/redactors/. - Personal-only: put the file in
.entire/redactors/local/—entire enablewrites that path into.entire/.gitignoreso personal rules don't pollute team commits.
Write a rule for:
- Internal service tokens (
ACME_*,INTERNAL_*) and custom env-var prefixes the bundled detectors don't know about. - Project-specific session formats.
- Project codenames or other identifiers you don't want stored in transcripts.
- Specific words or phrases you'd rather keep out of session archives.
File an issue when the rule would benefit every Entire user (e.g., a major SaaS issued a new token format), when a built-in is producing false positives on common idioms in your codebase, or when a built-in is not catching a well-known shared format (we'd rather fix the built-in than have everyone ship the same custom rule).
- My rule doesn't redact anything. Warnings about invalid patterns or sample mismatches are emitted by the redaction layer when Entire initializes it. In the hook path (where checkpoints are actually written) these go to
.entire/logs/entire.log—grep component=redactionand look for lines mentioning your label or pack path. When a hard pack-discovery failure happens during an interactive command, Entire also prints a one-line breadcrumb on stderr pointing back at the log. - My pack file is silently ignored. Filenames must end in
.yaml,.yml, or.json. Other extensions are skipped. - I want to disable a rule temporarily. Comment it out (prefix the YAML key with
#) or remove the entry fromcustom_redactions. The rule reloads on the next CLI invocation.
- Best-effort. Novel or low-entropy secrets (short passwords, predictable tokens) may not be caught.
- Filenames and binary data. Secrets in filenames, binary files, or deeply nested structures may not be detected.
- JSONL skip rules. Entire skips scanning fields whose name ends in
signature(sothinkingSignaturetoo), fields ending inid/ids, and structural-path fields (filepath,file_path,cwd,root,directory,dir,path) to avoid false positives. Objects whosetypestarts withimageor equalsbase64are also skipped — and that skip is what leaves a pasted image unredacted rather than merely unflagged. It matches some agents' image shapes and not others, which is why the outcome differs per agent: see What Entire does NOT understand: pasted images and screenshots above. - Built-in PII patterns are US-centric.
phonematches North American (NANP) formats only — international formats, including E.164 numbers outside+1, are not detected.addressmatches the street line only; city, state, and ZIP/postcode are preserved. If you handle personal data from other regions, addcustom_patternsfor your locale rather than relying on the built-in categories alone. - Custom PII patterns are user-authored. Teams own the correctness of their
custom_patterns. An invalid regex is logged and skipped, not enforced. - Users are ultimately responsible for reviewing what they commit and push. Redaction is a safety net, not a guarantee.
The CLI captures anonymous usage analytics by default. Sent to PostHog with DisableGeoIP enabled. Captured per command: command name, selected agent, whether Entire is enabled in the repo, CLI version, OS/arch, installed git version (best-effort; omitted if git is absent or unparseable), and names of flags passed (never their values). The distinct ID is a hashed machine identifier (machineid.ProtectedID), not a user identity.
Not captured: flag values, prompt text, transcripts, file paths, repository identifiers, GitHub usernames, source code.
Opt out via any one of:
--telemetry=falseon a command that accepts it."telemetry": falsein.entire/settings.jsonor.entire/settings.local.json.ENTIRE_TELEMETRY_OPTOUT=1in the environment.
external_agents turns on the $PATH scan that looks for entire-agent-*
binaries and runs each one's info subcommand, then keeps running the ones it
registered for every hook thereafter. It is an execution grant, not a
preference, so it is honored under exactly the same rule as the OPF
command: only from .entire/settings.local.json,
and only when that file is untracked in both the index and HEAD. Reading it
from the committed .entire/settings.json would let an ordinary pull request
turn on execution of whatever entire-agent-* binary it could get onto a
developer's $PATH, and — as with command — one line of JSON does not read as
executable to a reviewer. There is no prompt in front of this one at all.
Rejection is a downgrade, never an error: discovery simply does not run.
entire status names the setting and where it has to move, and the same reason
is logged. The interactive setup flows (entire enable, entire configure,
entire agent add, entire plugin uninstall) reach external agents regardless
of the setting, so the remedy stays available from the commands that need it —
and when one of them enables an external agent for you, it writes the setting to
.entire/settings.local.json, which is where it takes effect.
Every $PATH scanner in the CLI also drops non-absolute entries. A relative
entry resolves against the process's working directory, which for a git hook is
whatever repository the caller was standing in, so a file committed to that
repository would otherwise be a binary Entire executes.
For vulnerability disclosure, see SECURITY.md at the repo root: email security@entire.io, expect acknowledgment within 48 hours and resolution of criticals within 90 days.
- Checkpoint commit signing — best-effort GPG/SSH signing of checkpoint commits, opt-out via
sign_checkpoint_commits: false. - External agent plugins are arbitrary executables on
$PATHinvoked by the CLI; only install plugins you trust. Discovery is off unless you turn it on in.entire/settings.local.json— see Whyexternal_agentsis local-only.