{"slug": "audit-which-repositories-grok-build-collected-and-whether-xai-still-lists-the", "title": "Audit which repositories Grok Build collected and whether xAI still lists the archives", "summary": "A developer created an open-source Python script to audit which repositories Grok Build has collected and whether xAI still retains the archives. The script inspects local Grok logs to find upload events and optionally checks with xAI's API whether archive IDs are still recognized, without uploading or downloading any source code.", "body_md": "Paste this into Claude Code, Codex, or another local agent with filesystem and network access:\n\n```\nPerform a read-only audit of Grok Build codebase collection and retention on\nthis machine. Do the investigation with shell/Python; do not just give me\ninstructions. Do not launch Grok, send it a prompt, upload/download archives,\ninvoke /privacy, delete anything, or change configuration/account state. Never\nprint tokens, session IDs, or storage object IDs. Do not create or delete even\ntemporary files; process everything in memory or through stdin/stdout.\n\nInspect ~/.grok/logs/unified*.jsonl plus rotated logs beside it. Find\n`repo_state.upload.start` and `repo_state.upload.enqueued`. Correlate events by\n`sid`, `ctx.turn_number`, and phase; derive an enqueued event's phase from its\n`gcs_path` basename (`before_codebase.tar.gz` or `after_codebase.tar.gz`). Map\neach archive to the start event's `ctx.repo_path`.\n\nFor every repository, count collection attempts and queued archives and show\nfirst/last timestamps. Then perform a metadata-only existence check: read the\nnewest cached `key` from ~/.grok/auth.json without displaying it and POST the\nexact queued paths as `{\"paths\":[...]}` batches to:\nhttps://cli-chat-proxy.grok.com/v1/storage/batch_exists\nwith `Authorization: Bearer <token>`. Never call download or follow signed URLs.\n\nPrint the UTC check time and this table:\nSTATUS | PRESENT/ABSENT/UNKNOWN | ATTEMPTS | FIRST/LAST | REPOSITORY\n\nClassify COLLECTED-ONLY when collection started without a matching queue event;\nQUEUED when an archive was enqueued; REMOTE-PRESENT when any exact ID is in\n`exists`; NOT-LISTED only when all its IDs are in `missing`; otherwise UNKNOWN.\n\nFinish with totals and caveats: queueing alone does not prove server retention;\nREMOTE-PRESENT proves only that this API still recognizes the object;\nNOT-LISTED does not prove deletion from backups, replicas, derived data, or\nother stores; missing logs can cause false negatives. Warn that repository\npaths are sensitive before I share the output.\n```\n\n| #!/usr/bin/env python3 | |\n| \"\"\"Find repositories collected by Grok Build. | |\n| Usage: | |\n| python3 grok-upload-audit.py # read local Grok logs only | |\n| python3 grok-upload-audit.py --remote # also ask xAI if archive IDs exist | |\n| The remote check sends only logged archive IDs to xAI. It never uploads or | |\n| downloads source code and never prints credentials or archive IDs. | |\n| Labels: | |\n| COLLECTED-ONLY Grok's collector started; no matching queue event was found. | |\n| QUEUED An archive reached Grok's persistent upload queue. | |\n| REMOTE-PRESENT xAI's API still recognizes at least one exact archive ID. | |\n| NOT-LISTED xAI reports the IDs absent. This does not prove deletion from | |\n| backups, replicas, derived data, or other storage systems. | |\n| \"\"\" | |\n| import json | |\n| import os | |\n| import sys | |\n| import urllib.error | |\n| import urllib.request | |\n| from collections import defaultdict | |\n| from pathlib import Path | |\n| REMOTE = \"--remote\" in sys.argv | |\n| LOG = Path(os.getenv(\"GROK_LOG\", \"~/.grok/logs/unified.jsonl\")).expanduser() | |\n| AUTH = Path(os.getenv(\"GROK_AUTH\", \"~/.grok/auth.json\")).expanduser() | |\n| API = \"https://cli-chat-proxy.grok.com/v1/storage/batch_exists\" | |\n| if \"--help\" in sys.argv or \"-h\" in sys.argv: | |\n| raise SystemExit(__doc__) | |\n| if not LOG.is_file(): | |\n| raise SystemExit(f\"Grok log not found: {LOG}\") | |\n| starts = {} | |\n| repos = defaultdict(lambda: {\"attempts\": 0, \"archives\": [], \"last\": \"\"}) | |\n| for line in LOG.open(encoding=\"utf-8\", errors=\"replace\"): | |\n| try: | |\n| event = json.loads(line) | |\n| except json.JSONDecodeError: | |\n| continue | |\n| ctx = event.get(\"ctx\") or {} | |\n| msg, sid = event.get(\"msg\"), event.get(\"sid\") | |\n| turn, timestamp = ctx.get(\"turn_number\"), event.get(\"ts\") or \"\" | |\n| if msg == \"repo_state.upload.start\" and isinstance(ctx.get(\"repo_path\"), str): | |\n| repo = ctx[\"repo_path\"] | |\n| starts[sid, turn, ctx.get(\"phase\")] = repo | |\n| repos[repo][\"attempts\"] += 1 | |\n| repos[repo][\"last\"] = max(repos[repo][\"last\"], timestamp) | |\n| elif msg == \"repo_state.upload.enqueued\" and isinstance(ctx.get(\"gcs_path\"), str): | |\n| archive = ctx[\"gcs_path\"] | |\n| name = archive.rsplit(\"/\", 1)[-1] | |\n| if name not in {\"before_codebase.tar.gz\", \"after_codebase.tar.gz\"}: | |\n| continue | |\n| phase = name.removesuffix(\".tar.gz\") | |\n| repo = starts.get((sid, turn, phase), \"<unknown: earlier log missing>\") | |\n| repos[repo][\"archives\"].append(archive) | |\n| repos[repo][\"last\"] = max(repos[repo][\"last\"], timestamp) | |\n| paths = list(dict.fromkeys(p for row in repos.values() for p in row[\"archives\"])) | |\n| present, absent = set(), set() | |\n| if REMOTE and paths: | |\n| auth = json.loads(AUTH.read_text(encoding=\"utf-8\")) | |\n| entries = [v for v in auth.values() if isinstance(v, dict) and v.get(\"key\")] | |\n| if not entries: | |\n| raise SystemExit(\"No cached token found; run `grok login` first.\") | |\n| token = max(entries, key=lambda v: v.get(\"expires_at\", \"\"))[\"key\"] | |\n| for offset in range(0, len(paths), 100): | |\n| body = json.dumps({\"paths\": paths[offset : offset + 100]}).encode() | |\n| request = urllib.request.Request( | |\n| API, | |\n| data=body, | |\n| method=\"POST\", | |\n| headers={\"Authorization\": f\"Bearer {token}\", \"Content-Type\": \"application/json\"}, | |\n| ) | |\n| try: | |\n| with urllib.request.urlopen(request, timeout=20) as response: | |\n| result = json.load(response) | |\n| except urllib.error.HTTPError as error: | |\n| raise SystemExit(f\"Storage check failed with HTTP {error.code}.\") from None | |\n| except urllib.error.URLError as error: | |\n| raise SystemExit(f\"Storage check failed: {error.reason}\") from None | |\n| present.update(result.get(\"exists\", [])) | |\n| absent.update(result.get(\"missing\", [])) | |\n| home = str(Path.home()) | |\n| print(\"STATUS\\tARCHIVES\\tATTEMPTS\\tLAST EVENT\\tREPOSITORY\") | |\n| for repo, row in sorted(repos.items()): | |\n| archives = row[\"archives\"] | |\n| if not archives: | |\n| status, summary = \"COLLECTED-ONLY\", \"0\" | |\n| elif not REMOTE: | |\n| status, summary = \"QUEUED\", str(len(archives)) | |\n| else: | |\n| yes = sum(p in present for p in archives) | |\n| no = sum(p in absent for p in archives) | |\n| unknown = len(archives) - yes - no | |\n| status = \"REMOTE-PRESENT\" if yes else \"NOT-LISTED\" if no == len(archives) else \"UNKNOWN\" | |\n| summary = f\"{yes} present/{no} absent/{unknown} unknown\" | |\n| shown = \"~\" + repo[len(home) :] if repo.startswith(home + \"/\") else repo | |\n| print(f\"{status}\\t{summary}\\t{row['attempts']}\\t{row['last'] or '-'}\\t{shown}\") | |\n| print(\"\\nReview output before sharing: repository paths may be private.\") | |\n| print(\"NOT-LISTED is not proof of deletion from backups, replicas, or derived data.\") |", "url": "https://wpnews.pro/news/audit-which-repositories-grok-build-collected-and-whether-xai-still-lists-the", "canonical_source": "https://gist.github.com/hrkrshnn/ac1bafd2aa950a7e266afcc6660bb8d5", "published_at": "2026-07-13 19:46:34+00:00", "updated_at": "2026-07-15 14:23:20.690710+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-infrastructure"], "entities": ["Grok", "xAI", "Claude Code", "Codex"], "alternates": {"html": "https://wpnews.pro/news/audit-which-repositories-grok-build-collected-and-whether-xai-still-lists-the", "markdown": "https://wpnews.pro/news/audit-which-repositories-grok-build-collected-and-whether-xai-still-lists-the.md", "text": "https://wpnews.pro/news/audit-which-repositories-grok-build-collected-and-whether-xai-still-lists-the.txt", "jsonld": "https://wpnews.pro/news/audit-which-repositories-grok-build-collected-and-whether-xai-still-lists-the.jsonld"}}