18 / 20 Team Memory Sync src/services/teamMemorySync/index.ts · secretScanner.ts

Shared repo memory. Delta upload. Secret scanning.

Per-repo shared knowledge synced across all org members via /api/claude_code/team_memory. Uses SHA-256 content hashing for delta-only uploads. Files with detected secrets are silently skipped. Server always wins on pull; local wins on push. ETag-based optimistic locking.

250KB
Max entry — per memory file
200KB
Max PUT body — larger batches split
30+
Secret rules — gitleaks-based patterns
2
Max conflict retries (3 total attempts)

API Contract

MethodEndpointPurpose
GET/api/claude_code/team_memory?repo={owner/repo}Pull all data (includes entry checksums)
GET...?repo={owner/repo}&view=hashesMetadata + checksums only (no bodies — cheap probe)
PUT/api/claude_code/team_memory?repo={owner/repo}Upload entries (upsert semantics)
Auth required: First-party OAuth only with CLAUDE_AI_INFERENCE_SCOPE + CLAUDE_AI_PROFILE_SCOPE. Unavailable on Bedrock, Vertex, Foundry. Repo identity from git remote: owner/repo.

Delta Upload — Hash Comparison

Only changed files are uploaded. SHA-256 content hashing determines what's different.

1
Pull server checksums
GET returns entryChecksums: Map<key, "sha256:<hex>">. Stored in state.serverChecksums.
2
Compute local hashes
hashContent(content) = 'sha256:' + createHash('sha256').update(content, 'utf8').digest('hex')
3
Delta = keys where hashes differ
Only include keys where serverChecksums.get(key) !== localHash. Unchanged files skipped entirely.
4
Batch into 200KB PUT requests
Greedy bin-packing into MAX_PUT_BODY_BYTES chunks. Sorted keys for deterministic batches (ETag stability). Each batch is independent PUT.

Secret Scanning

30+ high-confidence gitleaks rules run BEFORE upload. Files with detected secrets are silently skipped — never uploaded.

What's scanned for

  • Cloud API keys (AWS, GCP, Azure)
  • GitHub / GitLab tokens
  • AI API keys (Anthropic, OpenAI)
  • Slack / Discord tokens
  • Private keys (RSA, SSH, PGP)
  • Database connection strings
  • JWT secrets and bearer tokens

How it works

scanForSecrets(content) runs compiled regex rules. Deduped by rule ID. Returns { ruleId, label } matches.

Secret VALUE never logged — only label + rule ID + file path. Compliant with PSR M22174.

Files with matches: skipped entirely (not uploaded, no partial redaction).

Conflict Resolution

Optimistic locking via ETags. On conflict (412), cheap probe + recompute delta + retry.

1
PUT with If-Match: "{etag}"
Optimistic lock — expects server state matches our last known ETag.
2
412 Precondition Failed — teammate pushed first
Another team member's concurrent push changed server state.
3
Cheap probe: GET ?view=hashes
Fetch only checksums (no bodies) — fast. Refresh serverChecksums with teammate's changes.
4
Recompute delta — only truly different keys remain
Keys that matched teammate's version naturally excluded. Only our unique changes sent.
5
Retry PUT — max 2 conflict retries (3 total)
Each batch carries updated ETag from previous response. Semantics: local wins for changed keys.

Sync Semantics

Pull (server → local)

  • Server always wins — remote entries overwrite local
  • New server keys create local files
  • Deletions don't propagate — if you delete locally, pull restores from server
  • 404 = no data exists yet (first-time repo)
  • If-None-Match ETag for conditional GET (304 = no changes)

Push (local → server)

  • Local wins for changed keys — your edits overwrite server version
  • Delta-only: unchanged files not sent
  • Secret-scanned files silently skipped
  • Batched into ≤200KB PUTs
  • If-Match ETag for optimistic locking
Bidirectional sync: syncTeamMemory() runs pull first (full, skip ETag cache), writes remote entries to local, then pushes local with conflict resolution. Returns { success, filesPulled, filesPushed, error }.

Settings Sync (Separate System)

User settings (not project/local/policy) uploaded to /api/claude_code/user_settings. 500KB max. Incremental — only changed entries. Used by CCR to sync config before plugin installation.

AspectTeam MemorySettings Sync
ScopePer-repo (git remote)Per-user (global)
DirectionBidirectional (pull + push)Upload only (CLI → cloud)
Max size250KB per entry500KB per file
Secret scanningYes (30+ rules)No
AvailabilityFirst-party OAuth onlyFirst-party OAuth only