Practical guide
Use Cloudflare R2 for Encrypted Coding-Agent Session Storage
To use Cloudflare R2 with Reinstate, create a private R2 bucket, issue an Object Read & Write S3 API token scoped to that bucket, initialize Reinstate with the account or jurisdiction endpoint and region auto, then dry-run and push one selected session.
BEFORE YOU START
Prerequisites
- A Cloudflare account with R2 enabled and authority to create a bucket and R2 API token
- Reinstate v0.1.0-rc.8 on a compatible device with Claude Code or Codex CLI
- A harmless session in a repository whose absolute local path you know
- A long encryption passphrase that will be entered privately and is not stored
Steps at a glance
Allow about 30 minutes after the prerequisites are ready. Network speed, storage configuration, or agent setup can extend that estimate.
- Create a private R2 bucket
Create one R2 bucket, choose its location or jurisdiction deliberately, leave public development URL and custom-domain access disabled, and record its name.
- Create bucket-scoped S3 credentials
Generate an R2 S3 API token with Object Read & Write permission for only the selected bucket, then store its access-key pair outside chat and source control.
- Install and check Reinstate
Install the pinned Reinstate release candidate, verify its version, and run the read-only setup check before writing local configuration.
- Initialize Reinstate with the R2 endpoint
Pass the account or jurisdiction-specific R2 S3 endpoint, region auto, bucket name, and project mapping to init, then enter credentials through hidden prompts.
- Dry-run, push, and verify ciphertext storage
Select one harmless Claude Code or Codex session, inspect a non-mutating push plan, push it, and verify only encrypted objects under the generated profile prefix.
This visible sequence is also published as truthful HowTo structured data. Valid structured data can help machines understand the page, but it does not guarantee a rich result. Google retired How-to rich results in 2023; the markup remains a Schema.org description for systems that consume it.
What this guide configures
This guide connects Reinstate v0.1.0-rc.8 to an existing Cloudflare R2
bucket through R2’s S3-compatible API. Cloudflare owns the account, bucket,
location, API token, public-access switches, retention, and billing. Reinstate
owns the local project mapping, encrypted profile manifest, encrypted session
snapshots, and same-vendor restore workflow.
| Responsibility | Cloudflare R2 | Reinstate |
|---|---|---|
| Create the bucket | Yes | No |
| Generate and revoke the R2 API token | Yes | No |
| Keep public URL and custom-domain access disabled | Yes | No |
| Test object write and delete access | Receives S3 API calls | rein init runs the probe |
| Encrypt session content before upload | No | Yes, locally with the passphrase |
| Choose the session and project | No | Yes |
| Empty or delete the R2 bucket | Yes | No |
rein init does not create an R2 bucket or token. It expects them to exist,
writes and deletes a two-byte probe beneath the new profile prefix, and saves
local configuration only after that probe succeeds. The first persistent
manifest.age appears after the first successful push, not after first-device
initialization.
Key points
- R2 buckets are private by default. Keep the public development URL and custom-domain access disabled because Reinstate uses authenticated S3 API requests.
- Use the R2 S3 endpoint from the token confirmation or account overview:
https://ACCOUNT_ID.r2.cloudflarestorage.com. Keep the bucket name separate and use--region auto. - EU or FedRAMP jurisdiction buckets require the matching
https://ACCOUNT_ID.JURISDICTION.r2.cloudflarestorage.comendpoint. - Choose Object Read & Write and apply the token to the specific bucket. Read-only access cannot complete Reinstate’s write-and-delete initialization probe.
- Reinstate encrypts the manifest and snapshots before R2 receives them. Cloudflare’s encryption at rest is an additional provider control.
- Session resume remains same-vendor: Claude Code to Claude Code and Codex CLI to Codex CLI.
- RC8 is a release candidate. This guide is not evidence that the outstanding physical two-device acceptance matrix has passed.
Before you begin
Use a dedicated R2 bucket for the first evaluation when practical. Cloudflare can scope an Object Read & Write token to one or more complete buckets, not to a generated Reinstate profile prefix through the dashboard flow. A dedicated bucket therefore gives the evaluation token a smaller and easier-to-audit scope.
An R2 account ID and bucket name are not authentication secrets, but they can still expose organizational identifiers. Redact them from public logs and screenshots. Never paste the access-key ID, secret access key, encryption passphrase, transcript, or downloaded snapshot into a coding-agent prompt.
Platform and release qualifications
| Environment | Installer path | Current qualification |
|---|---|---|
| macOS native arm64 | POSIX installer | Current source compatibility evidence covers the documented Claude Code and Codex ranges. |
| macOS native amd64 | POSIX installer | A Phase 1 release gate remains open; installer success is not certification. |
| Windows 11 native amd64 | PowerShell installer | A primary Phase 1 target whose native acceptance gate remains open. |
| Linux native | POSIX installer | Installer-compatible, but not a certified Phase 1 agent-resume target. |
| WSL2 amd64 | POSIX installer | Installer-compatible and documented for smoke testing, but its Phase 1 gate remains open. WSL1 is unsupported. |
Storage access does not override agent compatibility. rein setup check must
report SUPPORTED for the installed agent before a push or pull.
Command placeholders and parameters
| Value or flag | Meaning |
|---|---|
ACCOUNT_ID |
The non-secret Cloudflare account ID shown in R2 account details. Replace it in the endpoint. |
JURISDICTION |
Use only for a jurisdiction bucket, currently eu or fedramp; omit the segment for a default bucket. |
https://ACCOUNT_ID.r2.cloudflarestorage.com |
The default R2 S3 API service endpoint. It does not include the bucket name. |
YOUR_PRIVATE_BUCKET |
The R2 bucket name only. Do not append it to the endpoint. |
local/my-project |
A stable, non-secret project ID reused on every Reinstate device. |
/absolute/path/to/my-project |
The current device’s real absolute repository path. |
AGENT |
Replace with exactly claude or codex. |
SESSION_ID |
Copy the exact intended ID from rein list --agent AGENT. |
--region auto |
Uses the signing Region required by the R2 S3-compatible API. |
--session SESSION_ID |
Selects one session instead of every discovered session. |
--dry-run |
Authenticates, validates, and reports the plan without uploading the selected session. |
1. Create a private R2 bucket
In the Cloudflare dashboard:
- Open R2 object storage and create a bucket.
- Choose Automatic location, a location hint, or a jurisdiction according to your data-location requirements.
- Choose the intended default storage class.
- Leave the public development URL disabled.
- Do not connect a custom domain.
- Record the bucket name and whether it uses the default,
eu, orfedrampjurisdiction.
Cloudflare’s official R2 bucket guide documents creation and states that buckets are not public by default. Public access requires a separate action described in the public-bucket documentation; Reinstate does not need it. Location hints are best-effort, while jurisdiction selection controls the required endpoint and cannot later be changed. Review the R2 data-location guide before choosing.
Do not add a bucket lock to the evaluation prefix. R2 lock rules can prevent deletion and overwrite, while Reinstate’s init probe must be deleted and the encrypted manifest changes as sessions are pushed. Review R2 bucket-lock behavior before combining retention with a live sync profile.
Expected result: the R2 dashboard shows one private bucket with public development URL access disallowed and no custom domain. You have recorded only the non-secret bucket name, account ID, and jurisdiction needed to choose the S3 API endpoint.
2. Create bucket-scoped S3 credentials
From R2 Overview, open API Tokens and create an account or user token:
- Choose Object Read & Write.
- Choose Apply to specific buckets only.
- Select only the evaluation bucket.
- Create the token.
- Copy the generated Access Key ID, Secret Access Key, and S3 endpoint into a password manager. The secret is not shown again.
Cloudflare documents this exact provider flow in Get started with the R2 S3 API and explains account-token, user-token, bucket scope, and permission behavior in the R2 authentication reference. Object Read & Write is the least built-in permission that covers the read, list, write, and delete behavior needed by the current Reinstate backend.
RC8 accepts the generated access-key ID and secret access key. It does not accept an additional session-token field, so do not substitute R2 temporary credentials that require one. Do not use a general Cloudflare API bearer token; Reinstate talks to the S3-compatible API.
Expected result: the token is limited to Object Read & Write on the one selected bucket. It cannot administer unrelated buckets, public access, custom domains, lifecycle, or the rest of the Cloudflare account. You have stored both generated credential values privately and have the matching S3 API endpoint.
3. Install and check Reinstate
On macOS, Linux, or WSL2:
curl -fsSL https://reinstate.dev/install.sh | sh
On native Windows PowerShell:
irm https://reinstate.dev/install.ps1 | iex
If policy requires inspection first, use the download-and-review variants in the getting-started documentation.
rein version --json
rein setup check
Expected result: the pinned installer reports v0.1.0-rc.8. Before
initialization, rein setup check exits with code 3 and reports
config missing. Resolve a platform, keyring, or installed-agent
compatibility failure separately; working R2 credentials cannot make an
unsupported agent layout safe.
4. Initialize Reinstate with the R2 endpoint
For a default R2 bucket, replace every placeholder before running:
rein init \
--endpoint https://ACCOUNT_ID.r2.cloudflarestorage.com \
--region auto \
--bucket YOUR_PRIVATE_BUCKET \
--project local/my-project=/absolute/path/to/my-project
For a jurisdiction bucket, insert eu or fedramp between the account ID and
r2 exactly as Cloudflare shows:
https://ACCOUNT_ID.JURISDICTION.r2.cloudflarestorage.com
The endpoint is the R2 S3 service endpoint, not a public r2.dev URL, custom
domain, or bucket path. The first hidden prompt accepts the generated Access
Key ID; the second accepts its Secret Access Key. Do not put either value in
this command, an environment variable, shell history, or a coding-agent
conversation.
During initialization, RC8:
- generates a non-secret
profile_id; - derives the default
profiles/<profile_id>prefix; - sends a conditional two-byte probe object;
- deletes that probe;
- stores the R2 credential reference in config and the credential values in the operating-system keyring; and
- writes local
config.tomlandstate.jsononly after the remote probe succeeds.
Cloudflare’s current S3 compatibility table
lists the PutObject, GetObject, HeadObject, DeleteObject, and
ListObjectsV2 operations used by Reinstate and requires Region auto.
Then validate:
rein setup check
rein doctor --self-test
Expected result: initialization prints
initialized reinstate home (config.toml + state.json), a
profile_id=<UUID>, and a keyring credential reference. Both checks exit 0
when every installed-agent compatibility check also passes. The R2 bucket has
no persistent manifest yet; the temporary probe was removed.
5. Dry-run, push, and verify ciphertext storage
Use a harmless test session and replace AGENT with claude or codex:
rein list --agent AGENT
rein push --agent AGENT --session SESSION_ID --dry-run
rein push --agent AGENT --session SESSION_ID
rein status
Enter the encryption passphrase through Reinstate’s hidden prompt. It is
separate from the R2 secret access key and is not stored by Reinstate. Do not
use --all unless you deliberately intend to sync every discovered session.
Expected result: the preview reports
would push ... snapshot(s) with dry_run=true. The mutating command reports
pushed ... snapshot(s) with dry_run=false, and status reports a remote
revision. In the R2 dashboard, the exact generated prefix contains:
profiles/<profile_id>/manifest.age
profiles/<profile_id>/snapshots/<opaque-id>.age
There should be no plaintext transcript, auth file, .env file, access key,
or passphrase object. A .age filename alone is not cryptographic proof; the
committed Phase 1 acceptance runbook
defines the controlled ciphertext inspection used for release qualification.
This personal check does not claim physical RC8 acceptance.
Security boundaries
- Reinstate encrypts the session locally before upload. Cloudflare still sees the account, bucket, object sizes, request timing, and encrypted object keys.
- Cloudflare documents automatic encryption at rest and TLS-protected transfer for R2. Those provider controls are defense in depth, not a replacement for Reinstate’s client-side encryption. See the official R2 data-security reference.
- Compromise of the bucket alone should not reveal transcript plaintext without the Reinstate passphrase, but an attacker with write or delete access can still remove, replace, or roll back ciphertext. Encryption is not availability protection.
- Do not enable a public development URL or custom domain for session storage. A private S3 endpoint with scoped credentials is the intended access path.
- Known auth files, OAuth material, credential stores,
.envfiles, caches, and logs are excluded. A secret typed into a transcript is still transcript content and must be rotated if exposed.
Failure modes and common errors
Why does initialization report storage probe put failed (exit 4)?
Likely cause: Wrong endpoint, bucket, credential, jurisdiction, or a read-only token.
Safe next action: Compare the token confirmation endpoint and selected bucket; require Object Read & Write on that bucket only.
Why does initialization report storage probe cleanup failed (exit 4)?
Likely cause: Delete is denied or a bucket lock covers the generated probe prefix.
Safe next action: Preserve the error, remove the stray probe through approved R2 tooling, and fix the token or lock before retrying.
What causes SignatureDoesNotMatch or an authorization failure?
Likely cause: The account ID, jurisdiction endpoint, access-key pair, or Region is wrong.
Safe next action: Use the exact R2 S3 endpoint, --region auto, and the
credential generated for the selected bucket.
Why is config missing (exit 3) after failed init?
Likely cause: RC8 intentionally did not save local config because the remote probe failed.
Safe next action: Fix R2 first and rerun the same reviewed rein init.
What should I do after compatibility exit 5 or UNTESTED?
Likely cause: The installed coding-agent version or layout lacks release evidence.
Safe next action: Stop transfer and check the compatibility matrix.
Why does Reinstate report no matching local sessions found (exit 2)?
Likely cause: AGENT or SESSION_ID does not identify a discoverable
local session.
Safe next action: Run rein list --agent claude or
rein list --agent codex and select one exact ID.
Why is remote profile manifest not found on another device?
Likely cause: Profile UUID, bucket, prefix, endpoint, or jurisdiction differs from the first device.
Safe next action: Reuse every non-secret storage coordinate and the exact
first-device profile_id; do not create an empty manifest.
What should I do after conflict exit 6?
Likely cause: The local and remote copies diverged after sync.
Safe next action: Preserve the conflict record and both histories; resolve explicitly instead of deleting R2 objects.
Safe rollback and undo
Reinstate RC8 does not provide a general rein undo or remote-session delete
command.
- A push
--dry-runcreates no session object, so it needs no rollback. - A failed storage probe does not save
config.tomlorstate.json, although Reinstate may already have created its local home directories. - A successful first-device init has saved local configuration and the credential in the OS keyring, but its temporary R2 probe has been deleted.
- If the configuration is wrong, stop before pushing.
rein init --forcebacks up local config and state before replacement; it does not delete remote objects or revoke the R2 token. - After a push, never delete only
manifest.age. Stop every device, record the exact profile UUID, preserve needed backups, then remove the completeprofiles/<profile_id>/tree through approved R2 tooling only when its loss is acceptable. - Revoke the dedicated R2 API token after cleanup. Deleting local Reinstate data does not revoke the token or delete R2 objects.
Cloudflare documents that emptying or deleting a bucket is irreversible and that a bucket must be empty before deletion. Review the official R2 deletion procedure before any cleanup. Do not apply a short lifecycle rule as a casual undo: new objects uploaded while the rule is active are also subject to expiration.
Verification checklist
- The selected R2 bucket is private: its
r2.devdevelopment URL is disabled and no custom domain exposes the session objects. - The dedicated API token has Object Read & Write access only to the reviewed bucket, and its S3 endpoint matches the intended standard, European Union, or FedRAMP jurisdiction.
- Reinstate uses that exact endpoint with
--region auto, andrein setup checkplusrein doctor --self-testcomplete without a compatibility, keyring, filesystem, or crypto failure. - The reviewed
rein push --agent AGENT --session SESSION_ID --dry-runselects exactly one intended session and reports no mutation. - The real push succeeds,
rein statusreports the expected remote revision, and the bucket contains only age-encryptedmanifest.ageandsnapshots/*.ageobjects beneath the generated profile prefix. - No command output, shell history, commit, log, or screenshot contains the access-key secret or passphrase, and no physical two-device acceptance is claimed until the relevant release scenario has passed.
Current limitations
- Reinstate does not create or delete R2 buckets, API tokens, public-access settings, custom domains, lifecycle rules, storage classes, or bucket locks.
- RC8 accepts the access-key pair generated for an R2 API token but no additional session-token field.
- Dashboard token scope is bucket-level. RC8 generates the first profile UUID during initialization, so the token cannot be pre-scoped to that exact profile through this provider flow.
- Lifecycle expiration or an Infrequent Access policy can affect availability and cost. Reinstate does not coordinate R2 lifecycle or storage-class transitions.
- R2’s S3 compatibility evolves. This guide depends only on the current Put/Get/Head/Delete/List and conditional-write behavior used by RC8.
- Phase 1 transfers full immutable session snapshots; delta transfer, retention controls, and remote garbage collection remain later work.
- Current native resume is same-vendor only and the stable physical platform acceptance matrix remains incomplete.
Cloudflare R2 storage FAQ
Does the R2 bucket need a public URL or custom domain?
No. Leave the public development URL disabled and do not connect a custom domain. Reinstate authenticates directly to the private S3 API endpoint with the bucket-scoped credential.
Which R2 token permission does Reinstate need?
Use Object Read & Write and apply it only to the selected bucket. Read-only cannot write snapshots or delete the initialization probe. Admin Read & Write is broader than this workflow requires.
Why is the R2 Region auto?
Cloudflare’s S3 compatibility documentation specifies auto as the R2 Region.
The endpoint, not an AWS Region string, identifies the Cloudflare account and
any eu or fedramp jurisdiction.
Can several Reinstate profiles share one R2 bucket?
Yes. The default keyspace is profiles/<profile_id>/, so profiles have
separate object prefixes. A dedicated bucket is still simpler for token scope
and cleanup because the provider token is bucket-scoped, not
profile-prefix-scoped in this setup.
What happens if the R2 bucket is compromised?
An attacker who has only bucket contents should receive age-encrypted manifests and snapshots, not plaintext session data. An attacker with object permissions can still delete or replace ciphertext, observe metadata, and deny sync. Revoke the exposed R2 token, preserve local copies, and follow the security model.
How do I add the second computer?
Finish one successful push first so manifest.age exists. On the second
computer, run rein init --profile-id DEVICE_A_PROFILE_UUID with the same
endpoint, auto Region, bucket, token scope, passphrase, and canonical project
ID but that computer’s own absolute path. Then follow the agent-specific
Claude Code or Codex guide for the pull and native resume.