# Self-hosted retention and data subject requests

Source: https://codeherder.com/docs/self-host-retention-and-dsar/

How long each class of data is kept, the setting that changes it, and how to answer a data subject request, with backup residue.

Your organization runs the install, so your organization answers data subject requests. This page gives you the retention schedule and a procedure to follow. CodeHerder deletes data on a schedule. You can change most schedules.

## How data is erased

Three paths erase data. Each row below names the path that applies.

1. **Sweep.** The server deletes a row when its period ends. The setting column controls the period.
2. **Workspace delete.** Archive the workspace first. A delete of a workspace that is not archived fails with `409 not_archived`. Then run `ch workspace delete`. The delete is permanent. It removes the rows of the workspace and its child workspaces. It queues the attachment objects for deletion from the object store.
3. **Person erase.** Run `ch human erase`. See [Handle a data subject request](https://codeherder.com/docs/self-host-retention-and-dsar/#handle-a-data-subject-request).

These defaults are a starting point. Your organization approves its own retention schedule.

## Retention by data class

Each row names a class of data, its default period, and the setting that changes it. A setting is an environment variable on the server. A period of `0` turns the sweep off for most settings. Read the setting’s line in the configuration reference before you use `0`. A setting that the schedule marks “read at server start” needs a restart. Every other setting is read on each sweep.

| Data | Default | Setting | What you may change | Erased by |
| --- | --- | --- | --- | --- |
| Session exit tail (the last terminal output) | 30 days after the session ends | `CH_SESSION_EXIT_TAIL_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Execution observations | 90 days | `CH_EXECUTION_OBSERVATION_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Session resource usage | 90 days | `CH_SESSION_RESOURCE_USAGE_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Survey responses | 365 days. A pending invitation stays. | `CH_SURVEY_RESPONSE_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Calibration corpora | 365 days | `CH_CALIBRATION_CORPUS_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Agent questions | 365 days after the answer. An open question stays. | `CH_AGENT_QUESTION_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Messages | 365 days | `CH_MESSAGE_RETENTION_DAYS` | The period. `0` disables. | Sweep, workspace delete or person erase |
| Task comments | 730 days | `CH_TASK_COMMENT_RETENTION_DAYS` | The period. `0` disables. | Sweep, workspace delete or person erase (text becomes `[erased]`) |
| Workspace invitations | 90 days after acceptance, revocation or expiry. A live invitation stays. | `CH_WORKSPACE_INVITATION_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Sandboxes | 30 days after the sandbox is done or abandoned | `CH_SANDBOX_TERMINAL_RETENTION_DAYS` | The period. A value of 0 or less keeps the default. | Sweep after the period |
| Attempt outcomes and effort | 400 days | `CH_ATTEMPT_OUTCOME_RETENTION_DAYS` | The period. 0 or less disables. | Sweep after the period |
| Trial runs | 400 days | `CH_SHADOW_RUN_RETENTION_DAYS` | The period. 0 or less disables. | Sweep after the period |
| Compositions and outcomes | 730 days | `CH_COMPOSITION_RECORD_MAX_AGE_DAYS` | The period. | Sweep after the period |
| Events (activity and audit log) | 180 days. Device metric events: 7 days. Audit and milestone events are not pruned by the metrics sweep. | `CH_EVENTS_MAX_AGE_DAYS`, `CH_EVENTS_METRICS_RETENTION_DAYS` | Both periods. | Sweep after the period |
| Raw cost rows | 90 days | `CH_COST_RAW_RETENTION_DAYS`, `CH_COST_EVENTS_MAX_AGE_DAYS` | Both periods. `0` disables. Cost totals stay. | Sweep after the period |
| Server metrics | 30 days | `CH_SERVER_METRICS_RETENTION` | The period. | Sweep after the period |
| Device metrics history | 7 days | `CH_DEVICE_METRICS_HISTORY_RETENTION_DAYS` | The period. `0` disables. Read at server start. | Sweep after the period |
| Audit trace state | 48 hours for an open span | `CH_OTEL_SPAN_MAX_AGE` | The span age. | Sweep after the period |
| Agent notifications | 30 days delivered, 7 days pending | `CH_NOTIFICATION_DELIVERED_RETENTION_DAYS`, `CH_NOTIFICATION_PENDING_RETENTION_DAYS` | Each period. `0` disables. Read at server start. | Sweep after the period |
| Outbound webhook deliveries | 30 days. A pending row stays. | `CH_WEBHOOK_DELIVERY_RETENTION_DAYS` | The period. `0` disables. Use 1 day or more. | Sweep after the period |
| Inbound webhook deliveries | 30 days | `CH_INBOUND_WEBHOOK_DELIVERY_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Session API keys | 30 days after a session key is revoked | `CH_SESSION_API_KEY_RETENTION_DAYS` | The period. `0` disables. Read at server start. | Sweep after the period |
| Attach grants | 7 days past expiry (revoked rows) | `CH_ATTACH_GRANT_RETENTION_DAYS` | The period. `0` disables. Read at server start. | Sweep after the period |
| Device token last remote IP | 90 days after the token was last seen | `CH_DEVICE_TOKEN_REMOTE_IP_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| Idempotency cache | 1 hour | `CH_IDEMPOTENCY_RETENTION` | The period. Read at server start. | Sweep after the period |
| Pending registrations and email changes | 24 hours | `CH_PENDING_IDENTITY_RETENTION_HOURS` | Lengthen only. The server floors it at 24 hours. | Sweep after the period |
| Shared document change log | 30 days | `CH_SHARED_DOC_CHANGES_RETENTION_DAYS` | The period. `0` disables. | Sweep after the period |
| MCP OAuth clients | 7 days | `CH_MCP_OAUTH_CLIENT_MAX_AGE_DAYS` | The period. | Sweep after the period |
| Agent-created (ephemeral) workspaces | 72 hours, unless a person joined | `CH_EPHEMERAL_WORKSPACE_TTL_HOURS` | The period. | Sweep after the period |
| Short protocol windows (OAuth requests and grants, upload staging) | Minutes to 24 hours | none | Nothing. These bound a security window. | Expiry of the window |

The server also keeps a terminal attach buffer in its own memory. It ends after 6 hours idle. `CH_ATTACH_RING_IDLE_TTL` is a server setting that changes it. A server restart clears it.

These classes have no timer. They stay until you delete the owner:

| Data | Kept until | Erased by |
| --- | --- | --- |
| Tasks, task fields and their history; session records; repositories; workflows, stages and skills; optimization data; reports; alerts; wiki pages; attachments; cost totals | The workspace is deleted. Archive hides an item. It does not delete it. | Workspace delete |
| People and access (`humans`, `members`, memberships, teams) | The person is erased. See [Handle a data subject request](https://codeherder.com/docs/self-host-retention-and-dsar/#handle-a-data-subject-request). | Person erase |
| Devices and AI accounts | The workspace of the device owner is deleted. Archive hides a device. It does not delete it. A device stays when another workspace it serves is deleted. | Workspace delete of the owner’s home workspace. The device’s AI accounts and tokens go with it. |
| Secrets and credentials (variables, launch configurations, device tokens, SSO connections, webhook endpoints) | You revoke, rotate or delete them. | Revoke, rotate or delete |
| Branding, SSO and import configuration | The workspace is deleted. | Workspace delete |
| The authentication audit log and the instance audit log | The instance is deleted. No sweep prunes them. | None. It lasts for the life of the instance. |
| The revocation journal | The instance is deleted. Nothing prunes it. | None. It lasts for the life of the instance. |

Every class above stays in the stores listed in [Erased data in backups](https://codeherder.com/docs/self-host-retention-and-dsar/#erased-data-in-backups) until each store expires it. That is the backup residue of every class.

Server log retention is a separate setting. See [Self-hosted log retention and access](https://codeherder.com/docs/self-host-log-retention/) for the log classes.

## Data on devices

The device keeps its own copies. The transcript archive keeps 7 days. Set `CH_TRANSCRIPT_ARCHIVE_RETENTION_DAYS` on the device to change it. `0` keeps the archive. The device log files keep up to 8 MB (`CH_LOCAL_LOG_BUDGET_MB`). Worktrees stay on the device until you remove them. See [What a device keeps](https://codeherder.com/docs/self-host-departure/#what-a-device-keeps).

## Handle a data subject request

Follow these steps in order. You need instance operator rights.

1. **Find the person’s data.**
  - Run `ch member list` in each workspace. Match on email or member id.
  - Run `ch member show <ref>`. Run `ch member tasks <ref> --role filed` and `--role worked`.
  - Run `ch task list --created-by <uuid>`.
  - Search messages and comments the person wrote.
  - Search the instance audit log and the authentication audit log for the person’s email.
2. **Export.** Run `ch workspace export <workspace> --out <file>.zip` for each workspace the person belongs to. The archive holds the workspace’s records and attachments. It leaves out secret values, transcripts and launch configurations. The archive covers the whole workspace. Pick out the person’s rows before you release anything. These rows are members, humans, invitations, events by the person, comments and messages.
3. **Review work product.** The erase keeps task titles and text, wiki pages and attachments, because the team owns them. Edit or archive personal text before you erase.
4. **Erase.** Run `ch human erase <ref>` for a dry run. Read the row counts. Then run `ch human erase <ref> --apply --yes`. The erase refuses a person who owns a device or device token, operates an agent, owns a persona wiki page, holds a live API key, has a live session, or holds a membership outside their home account. Revoke or transfer each one first. `ch human revoke-all` revokes credentials. Deprovision removes access. See [Offboard a person](https://codeherder.com/docs/self-hosting/#offboard-a-person). The erase removes the member and human rows, the messages the person sent and the keys that hang from the member. It replaces the person’s comment text with `[erased]` and removes personal keys from their events. The erase cannot be undone.
5. **Identity provider step.** Read `identityDeletion` in the erase response.
  - **Cognito mode.** The server deletes the Cognito user after the database commit. The value is `deleted`. If the value is `failed`, delete the user by hand. The response names the subject. The server role needs `cognito-idp:AdminDeleteUser`. See [Grant the lifecycle permissions](https://codeherder.com/docs/self-host-cognito/#grant-the-lifecycle-permissions).
  - **OIDC mode.** The value is `not_managed`. The user is your corporate identity. CodeHerder does not delete it. Whether to delete or keep it is your decision, because the person may still work for you. Write the decision in the DSAR record. The erased member does not come back if the person signs in again.
6. **Handle what the erase does not reach.**
  - Revoke pending invitations to the person’s email.
  - Remove the person from webhook receivers, SIEM and log stores under your own rules.
  - Remove device-side data (see above).
  - Instance audit log rows for an erased operator stay. This is the audit trail.
7. **Keep a record.** Write the request date, the person, the workspaces, the export files, the erase response, the identity provider action and the date the last backup expires.

## Erased data in backups

An erase changes the live database only. Erased rows stay in each of these stores until the store expires them.

| Store | How long the erased data stays |
| --- | --- |
| RDS automated backups and point-in-time recovery | Until the backup retention period ends. You set it. Seven days is a common start. See [Back up the database](https://codeherder.com/docs/self-host-backups/#back-up-the-database). |
| Manual RDS snapshots and `pg_dump` files | Until you delete them. |
| Attachments bucket, older object versions and replicas | Until your lifecycle rule expires them. The erase keeps the live object. |
| Server, proxy and database logs | The journald or log store retention. See [Self-hosted log retention and access](https://codeherder.com/docs/self-host-log-retention/). |
| Webhook receivers and audit receivers | The receiver’s retention. |
| Revocation journal | It holds the person id and a time only. It holds no name or email. Keep it at least as long as your longest backup. |
| Device transcript archive | 7 days by default. |
| Cognito user pool | The pool is not part of the database restore. If you roll the pool back, delete the user again. |

A restore brings erased rows back. After a restore, run `ch instance restore-fence replay --yes`. It applies again each erase made after the recovery point. See [Recover](https://codeherder.com/docs/self-host-backups/#recover). The data is gone everywhere on the date the request closes plus the longest window in the table.
