Compare commits

..

1 Commits

Author SHA1 Message Date
Mohamed Hegazy
43fcf6d513 security-guidance: encode exception type + errno + ensurepip instrumentation for venv BUILD_FAILED (2.0.3 → 2.0.4)
Follow-up to #2154. v2.0.3 telemetry showed the venv BUILD_FAILED bucket
splits into two unexplained groups; this PR instruments both.

## 1. The exc: bucket — exception type + errno

The dominant remaining venv BUILD_FAILED (phase=venv, err=99) is ~99%
sdk_bootstrap_stderr_sig=NULL — Python exceptions caught by the generic
`except Exception` ("exc:<TypeName>"), not CalledProcessErrors with
categorizable stderr. ~56k/30h, all opaque (stderr_sig only covers
"other:<tail>").

  - Handler embeds errno for OSError-family: "exc:OSError:28", etc.
  - SDK_BOOTSTRAP_EXC_CODES maps the type → sdk_bootstrap_exc
    (FileNotFoundError=1 … OSError=6 … 99=other).
  - errno decoded → sdk_bootstrap_errno (ENOENT/EACCES/ENOSPC/…).

## 2. venv_ensurepip_fail instrumentation (the other category)

venv_ensurepip_fail (code 11) is the top categorizable venv failure, and
telemetry flipped the naive assumption: it's NOT just Debian/Ubuntu —
macOS has the MOST distinct affected users (466 vs 121 linux), and linux
is a retry storm (~172 fires/user). Before committing to a `pip install
--target` fallback (Option A) we need to know (a) which interpreter these
users run and (b) whether that interpreter even has pip (→ whether
--target would work, vs needing a system package).

  - sdk_hook_py (always emitted): interpreter version as major*100+minor
    (309/312). Disambiguates Apple-3.9 vs a 3.10+-with-broken-ensurepip,
    and also recovers the version for HOOK_PY_INCOMPATIBLE (whose "py_3.9"
    err_kind otherwise collapses to err=99).
  - sdk_has_pip (only on err==11, to avoid an extra subprocess per healthy
    session): whether `<interpreter> -m pip --version` works. has_pip=true
    → the --target fallback would fix them; has_pip=false → they need a
    system package (python3-venv / a complete Python).

Both #1 and #2 are purely additive telemetry on the existing BUILD_FAILED
path — no behavior change to the bootstrap. They de-risk the Option A
decision: ship A only if the affected cohort has pip.

Verified locally on macOS Python 3.13:
  - py_compile clean.
  - 39 tests in test_exc_failure_encoding.py (34 exc/errno + 5 ensurepip
    instrumentation): type-code map, errno extraction + round-trip,
    APPEND-ONLY stability, handler-embeds-errno, _probe_has_pip returns
    bool + true-on-this-machine, sdk_hook_py always-emitted as
    major*100+minor, sdk_has_pip gated on err==11.
  - Full suite: 503/503 pass + 2 skipped.

Version 2.0.3 -> 2.0.4 per the per-PR-bump policy (#2114).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 14:55:55 -07:00
12 changed files with 202 additions and 376 deletions

View File

@@ -19,7 +19,7 @@
"url": "https://github.com/42Crunch-AI/claude-plugins.git",
"path": "plugins/api-security-testing",
"ref": "v1.5.5",
"sha": "db2fb7e53e3d93a863930b6f6b7895be5ee01f21"
"sha": "1db609845441d4fa8862019191e4138e61f77e67"
},
"homepage": "https://42crunch.com"
},
@@ -57,7 +57,7 @@
"source": {
"source": "url",
"url": "https://github.com/SalesforceAIResearch/agentforce-adlc.git",
"sha": "fad761fce6cba119d23792b3a96a3bf33e23c566"
"sha": "1db738befed88c2ee6d068482cfd64a10c97e2ef"
},
"homepage": "https://github.com/SalesforceAIResearch/agentforce-adlc"
},
@@ -127,20 +127,6 @@
},
"homepage": "https://cloud.google.com/alloydb"
},
{
"name": "alloydb-omni",
"description": "Create, connect, and interact with an AlloyDB Omni database and data.",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/alloydb-omni.git",
"sha": "fbf2476630629f32ce0029bbd62d225950fdfd6d"
},
"homepage": "https://github.com/gemini-cli-extensions/alloydb-omni"
},
{
"name": "amazon-location-service",
"description": "Guide developers through adding maps, places search, geocoding, routing, and other geospatial features with Amazon Location Service, including authentication setup, SDK integration, and best practices.",
@@ -191,7 +177,7 @@
"source": {
"source": "url",
"url": "https://github.com/apollographql/skills.git",
"sha": "605089108a198e412f7f0c1926c91eb94a6d1727"
"sha": "9ccf13477e116ec095ba9b606212492ffbd42926"
},
"homepage": "https://www.apollographql.com"
},
@@ -275,7 +261,7 @@
"url": "https://github.com/auth0/agent-skills.git",
"path": "plugins/auth0",
"ref": "main",
"sha": "bdf0dc23f8b17446b2c94bc9f2e5a58d3f1bc114"
"sha": "9d93554c5d91bd087a46f4d6825f80c3eb981945"
},
"homepage": "https://auth0.com/docs/quickstart/agent-skills"
},
@@ -291,7 +277,7 @@
"url": "https://github.com/aws/agent-toolkit-for-aws.git",
"path": "plugins/aws-agents",
"ref": "main",
"sha": "55b9acfefdcf0866b6bc6cc56c16e6e18e65bd2b"
"sha": "df13dea64baaa1b7031b25d1b2f380756131efec"
},
"homepage": "https://github.com/aws/agent-toolkit-for-aws"
},
@@ -320,7 +306,7 @@
"url": "https://github.com/aws/agent-toolkit-for-aws.git",
"path": "plugins/aws-core",
"ref": "main",
"sha": "55b9acfefdcf0866b6bc6cc56c16e6e18e65bd2b"
"sha": "df13dea64baaa1b7031b25d1b2f380756131efec"
},
"homepage": "https://github.com/aws/agent-toolkit-for-aws"
},
@@ -336,7 +322,7 @@
"url": "https://github.com/aws/agent-toolkit-for-aws.git",
"path": "plugins/aws-data-analytics",
"ref": "main",
"sha": "55b9acfefdcf0866b6bc6cc56c16e6e18e65bd2b"
"sha": "df13dea64baaa1b7031b25d1b2f380756131efec"
},
"homepage": "https://github.com/aws/agent-toolkit-for-aws"
},
@@ -381,7 +367,7 @@
"url": "https://github.com/awslabs/startups.git",
"path": "advisor/plugins/aws-startup-advisor",
"ref": "main",
"sha": "1dd909352dc228f978c2685724cb38e64efe6be4"
"sha": "30808e64b08ba13aedcecade5a27bfbff06dba09"
},
"homepage": "https://github.com/awslabs/startups"
},
@@ -392,7 +378,7 @@
"source": {
"source": "url",
"url": "https://github.com/microsoft/azure-skills.git",
"sha": "02a614f6ee1f052826f834d65c61e430ad152c8e"
"sha": "58fd90942ab5045481bf1632fa0c2d7746367e13"
},
"homepage": "https://github.com/microsoft/azure-skills"
},
@@ -434,20 +420,6 @@
},
"homepage": "https://docs.bigdata.com"
},
{
"name": "bigquery-data-analytics",
"description": "Connect, query, and generate data insights for BigQuery datasets and data.",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/bigquery-data-analytics.git",
"sha": "9cee2a03105d74648231ed3a5c4a63c4f194790d"
},
"homepage": "https://github.com/gemini-cli-extensions/bigquery-data-analytics"
},
{
"name": "box",
"description": "Work with your Box content directly from Claude Code — search files, organize folders, collaborate with your team, and use Box AI to answer questions, summarize documents, and extract data without leaving your workflow.",
@@ -472,7 +444,7 @@
"source": {
"source": "url",
"url": "https://github.com/brightdata/skills.git",
"sha": "bd5bd76bc889f54b744bab3db3cbd42751a1e5b0"
"sha": "68651246ad1819b98a1fc15ce10239e55406ff37"
},
"homepage": "https://docs.brightdata.com"
},
@@ -502,7 +474,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-cap-table",
"ref": "main",
"sha": "9eb312908f4a2e2d15e4e935320981433a549f77"
"sha": "ea02da68e7be8bf4bc2bffe8f1fd7253f8d0b101"
},
"homepage": "https://carta.com"
},
@@ -518,7 +490,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-crm",
"ref": "main",
"sha": "9eb312908f4a2e2d15e4e935320981433a549f77"
"sha": "ea02da68e7be8bf4bc2bffe8f1fd7253f8d0b101"
},
"homepage": "https://carta.com"
},
@@ -534,7 +506,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-investors",
"ref": "main",
"sha": "9eb312908f4a2e2d15e4e935320981433a549f77"
"sha": "ea02da68e7be8bf4bc2bffe8f1fd7253f8d0b101"
},
"homepage": "https://carta.com"
},
@@ -561,7 +533,7 @@
"source": {
"source": "url",
"url": "https://github.com/ChromeDevTools/chrome-devtools-mcp.git",
"sha": "702d3734f276a18efd67561ae00b88ce954cc515"
"sha": "89718901174be7c0c58a1a2b29281ab2f053cd53"
},
"homepage": "https://github.com/ChromeDevTools/chrome-devtools-mcp"
},
@@ -655,7 +627,7 @@
"source": {
"source": "url",
"url": "https://github.com/ClickHouse/clickhouse-claude-code-plugin.git",
"sha": "ecbd47627d7e7b3de15b297b91e0abf3e6ebc746"
"sha": "1f30864b720960a797e5c7f6138d328bec3984cb"
},
"homepage": "https://github.com/ClickHouse/clickhouse-claude-code-plugin"
},
@@ -669,24 +641,10 @@
"source": {
"source": "url",
"url": "https://github.com/ClickHouse/agent-skills.git",
"sha": "544384f4fab1d6ed59f16a354d1c68296dfa6007"
"sha": "46ef08ccf32fa28587b64e0c79106ff437dc8fcb"
},
"homepage": "https://clickhouse.com"
},
{
"name": "cloud-sql-mysql",
"description": "Connect and interact with a Cloud SQL for MySQL database and data.",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/cloud-sql-mysql.git",
"sha": "983c804fe7dc58b3e58021960e7e1831a10e08b9"
},
"homepage": "https://github.com/gemini-cli-extensions/cloud-sql-mysql"
},
{
"name": "cloud-sql-postgresql",
"description": "Create, connect, and interact with a Cloud SQL for PostgreSQL database and data.",
@@ -701,26 +659,12 @@
},
"homepage": "https://cloud.google.com/sql"
},
{
"name": "cloud-sql-sqlserver",
"description": "Connect to Cloud SQL for SQL Server",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/cloud-sql-sqlserver.git",
"sha": "8e1490ec8f659a5711655d2fa4241597a63d4883"
},
"homepage": "https://github.com/gemini-cli-extensions/cloud-sql-sqlserver"
},
{
"name": "cloudflare",
"source": {
"source": "url",
"url": "https://github.com/cloudflare/skills.git",
"sha": "c5b7b06b073fa0b4abbd63964630f97d81da69c4"
"sha": "60147cbb773649eadca89cee92b4e0caf02234b4"
},
"description": "Skills for the Cloudflare developer platform: Workers, Durable Objects, Agents SDK, MCP servers, Wrangler CLI, and web performance.",
"category": "deployment",
@@ -804,7 +748,7 @@
"source": {
"source": "url",
"url": "https://github.com/CodSpeedHQ/codspeed.git",
"sha": "c6112f168b405df8e7310b12a9b80484cd01ac14"
"sha": "f79d57d207f039e44a31a976564715f7731e71b6"
},
"homepage": "https://codspeed.io"
},
@@ -872,7 +816,7 @@
"source": {
"source": "url",
"url": "https://github.com/CrowdStrike/foundry-skills.git",
"sha": "c542c932956fd19177a62b94577f288c832d4680"
"sha": "b3f4ecb48333d6007117a29650daa1989a228b5c"
},
"homepage": "https://github.com/CrowdStrike/foundry-skills"
},
@@ -918,7 +862,7 @@
"source": {
"source": "url",
"url": "https://github.com/dash0hq/dash0-agent-plugin.git",
"sha": "5ff7aa5b8e52e10d10e45ea8e2f7cbebc86758bf"
"sha": "8801a21931d80c543c0f51a4b7eef4cd1311c1b5"
},
"homepage": "https://dash0.com/"
},
@@ -998,20 +942,6 @@
},
"homepage": "https://datahub.com"
},
{
"name": "dataproc",
"description": "Manage Dataproc clusters and jobs.",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/dataproc.git",
"sha": "20eec06eee7683311689f4a1437cbb14ac8cd33e"
},
"homepage": "https://github.com/gemini-cli-extensions/dataproc"
},
{
"name": "datarobot-agent-skills",
"description": "DataRobot skills for AI/ML workflows — model training, deployment, predictions, feature engineering, monitoring, explainability, data preparation, App Framework CI/CD, and external agent monitoring.",
@@ -1022,7 +952,7 @@
"source": {
"source": "url",
"url": "https://github.com/datarobot-oss/datarobot-agent-skills.git",
"sha": "b5a8f7a4bc4d31a1f139a232efbba6127af0474a"
"sha": "90a33c0c87362f28be88c14c0ef0f3469e6d2596"
},
"homepage": "https://datarobot.com"
},
@@ -1035,7 +965,7 @@
"url": "https://github.com/microsoft/Dataverse-skills.git",
"path": ".github/plugins/dataverse",
"ref": "main",
"sha": "2d50cf65f80efc17ac50632222d61fb374115a70"
"sha": "ab906c960db0f2da83c2cb92a3fd162ccaba9cb9"
},
"homepage": "https://github.com/microsoft/Dataverse-skills"
},
@@ -1064,7 +994,7 @@
"url": "https://github.com/wonderwhy-er/DesktopCommanderMCP.git",
"path": "plugins/claude",
"ref": "main",
"sha": "7a9b2ff0339a7fdc29c06a9957b323ef478a1dde"
"sha": "cf857bf061cb3b0e8673717dcac1f0fa2ecbdd40"
},
"homepage": "https://desktopcommander.app"
},
@@ -1126,7 +1056,7 @@
"source": {
"source": "url",
"url": "https://github.com/exa-labs/exa-mcp-server.git",
"sha": "f08388256c5806f457fae777b5528eb02a48e703"
"sha": "ad888a188cdefbe832c9feed2c3a97d1cb93cb35"
},
"homepage": "https://exa.ai/docs/reference/exa-mcp"
},
@@ -1150,7 +1080,7 @@
"url": "https://github.com/expo/skills.git",
"path": "plugins/expo",
"ref": "main",
"sha": "c38860242118df93d4ec4381a34f4144fff61928"
"sha": "fdd3df12151a208853fe540ffea9a67773446377"
},
"homepage": "https://github.com/expo/skills/blob/main/plugins/expo/README.md"
},
@@ -1220,20 +1150,6 @@
},
"homepage": "https://github.com/firecrawl/firecrawl-claude-plugin.git"
},
{
"name": "firestore-native",
"description": "Connect and interact with Firestore databases, collections, and documents.",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/firestore-native.git",
"sha": "f88103bd0ccfe9e1e7a3a7d849de26d197978c9a"
},
"homepage": "https://github.com/gemini-cli-extensions/firestore-native"
},
{
"name": "forge-skills",
"description": "Forge-focused skills and MCP configuration for Atlassian Forge: scaffold and deploy apps (forge create, templates, dev spaces), build Teamwork Graph connectors for Rovo Search/Rovo Chat, pre-deploy review, systematic debugging, plus Forge docs and Atlassian Design System lookups via MCP.",
@@ -1244,7 +1160,7 @@
"source": {
"source": "url",
"url": "https://github.com/atlassian/forge-skills.git",
"sha": "02103cca4addb4c42d64d4e18a9d1a7f186edf6c"
"sha": "2014fae5b1529a22629129b1564ae522593eb46d"
},
"homepage": "https://developer.atlassian.com/platform/forge/"
},
@@ -1270,7 +1186,7 @@
"source": "github",
"repo": "fullstorydev/fullstory-skills",
"commit": "1ec5865e7ab1449f9a0859d164c4b6a8c53b6e2f",
"sha": "b20614e2d08d7a7c70775bb62b5af640f60b024b"
"sha": "384555c3919a0631a096de1172998c8d855a0f26"
},
"homepage": "https://www.fullstory.com"
},
@@ -1333,7 +1249,7 @@
"source": {
"source": "url",
"url": "https://github.com/huggingface/skills.git",
"sha": "d7223848c3895fbd447faf2aec73e0a6cdd7fdcd"
"sha": "14cea99d5cd028974dbdd8bc12118882cd7a1b67"
},
"homepage": "https://github.com/huggingface/skills.git"
},
@@ -1347,7 +1263,7 @@
"source": {
"source": "url",
"url": "https://github.com/hunter-io/claude-plugin.git",
"sha": "494b0bd6ac252c7c8d78402cb51c7f635b1469ad"
"sha": "69c4e59ee573f4ccd8aa38bbc89e356bc8e7f876"
},
"homepage": "https://hunter.io"
},
@@ -1361,7 +1277,7 @@
"source": {
"source": "url",
"url": "https://github.com/heygen-com/hyperframes.git",
"sha": "25420bf4cfc37b179b4efeace9db25a7178b61bf"
"sha": "8228932e17e3371d5cf77ac5d5988f5322892dad"
},
"homepage": "https://hyperframes.heygen.com"
},
@@ -1415,24 +1331,10 @@
"source": "github",
"repo": "jfrog/claude-plugin",
"commit": "259c8e718266c16e99b4f30ae9b1ed0f9f00d98d",
"sha": "117febaa29cbe9449cfb42d1c39b83b858d801a1"
"sha": "8324c7fc9a5561398fe57b8a56db53bdbf1e2cda"
},
"homepage": "https://jfrog.com"
},
{
"name": "knowledge-catalog",
"description": "Connect to Knowledge Catalog to discover, manage, monitor, and govern data and AI artifacts across your data platform",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/knowledge-catalog.git",
"sha": "317e96fdd12aa61778b950192aff627efdc21099"
},
"homepage": "https://github.com/gemini-cli-extensions/knowledge-catalog"
},
{
"name": "kotlin-lsp",
"description": "Kotlin language server for code intelligence",
@@ -1544,20 +1446,6 @@
},
"homepage": "https://github.com/pydantic/skills/tree/main/plugins/logfire"
},
{
"name": "looker",
"description": "Connect to Looker and interact with your data using LookML.",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/looker.git",
"sha": "e912c0342f1bfd436e9236aaef7cc732239c80f7"
},
"homepage": "https://github.com/gemini-cli-extensions/looker"
},
{
"name": "lua-lsp",
"description": "Lua language server for code intelligence",
@@ -1643,7 +1531,7 @@
"url": "https://github.com/modelcontextprotocol/ext-apps.git",
"path": "plugins/mcp-apps",
"ref": "main",
"sha": "ca1d29894fabbd1558885a9ec8620dcb01d7457e"
"sha": "a9907802937f1da067cbc4aa48b283cd4cfa7dc8"
},
"homepage": "https://modelcontextprotocol.io"
},
@@ -1708,7 +1596,7 @@
"url": "https://github.com/awslabs/startups.git",
"path": "migrate/plugins/migration-to-aws",
"ref": "main",
"sha": "1dd909352dc228f978c2685724cb38e64efe6be4"
"sha": "30808e64b08ba13aedcecade5a27bfbff06dba09"
},
"homepage": "https://github.com/awslabs/startups"
},
@@ -1770,7 +1658,7 @@
"source": {
"source": "url",
"url": "https://github.com/netlify/context-and-tools.git",
"sha": "5f777ba63df12f4eb189be4c58bd35d0c8316505"
"sha": "cffaf74f79128620b8200956222aeb819f5f8fd5"
},
"homepage": "https://github.com/netlify/context-and-tools"
},
@@ -1839,7 +1727,7 @@
"url": "https://github.com/NVIDIA/skills.git",
"path": "plugins/nvidia-skills",
"ref": "main",
"sha": "0482ebce81bd8f2d39990317bb3cfb07637e39fd"
"sha": "e695a8397463bbb64d787b3cd88d3c58889be633"
},
"homepage": "https://github.com/NVIDIA/skills"
},
@@ -1855,24 +1743,10 @@
"url": "https://github.com/oracle-samples/oracle-aidp-samples.git",
"path": "ai/claude-code-plugins/oracle-ai-data-platform-workbench-spark-connectors",
"ref": "main",
"sha": "00cedef34c99d642d969f87965736768de01cbd6"
"sha": "dcd5a5a19537bf9aaa9dd4f48514bc4402bfbc40"
},
"homepage": "https://docs.oracle.com/en/cloud/paas/ai-data-platform/index.html"
},
{
"name": "oracledb",
"description": "Connect, query, and interact with Oracle Databases and their data.",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/oracledb.git",
"sha": "56239109760fd8ea838a56c946400347467bfa6d"
},
"homepage": "https://github.com/gemini-cli-extensions/oracledb"
},
{
"name": "outputai",
"description": "Output.ai workflow development toolkit for Claude Code. Adds 5 specialist agents (planner, builder, debugger, prompt writer, quality reviewer), 40+ slash-command skills covering scaffolding, debugging, evaluation, and credential management, plus a SessionStart hook that auto-loads Output SDK conventions so Claude understands the framework before the first prompt.",
@@ -1885,7 +1759,7 @@
"url": "https://github.com/growthxai/output.git",
"path": "coding_assistants/claude/plugins/outputai",
"ref": "main",
"sha": "2cc4685ebadfba9586f01890df48e1b25bd1049a"
"sha": "d3c9b1f472358527386f7cc2bb6d4833d9bfe034"
},
"homepage": "https://output.ai"
},
@@ -1933,7 +1807,7 @@
"source": {
"source": "url",
"url": "https://github.com/gopigment/ai-plugins.git",
"sha": "f7bb2190a3f072bd9be5175bde6a0aa9596fcaaa"
"sha": "abf36e64750d1323a4cc5fe79161597668231224"
},
"homepage": "https://www.pigment.com"
},
@@ -1995,7 +1869,7 @@
"source": {
"source": "url",
"url": "https://github.com/PostHog/ai-plugin.git",
"sha": "db4a86632293ca66eec9a6d278786ddb22c1787e"
"sha": "a487311487bc369ee75e70c893d0a0c5ed478ba8"
},
"homepage": "https://posthog.com/docs/model-context-protocol"
},
@@ -2088,7 +1962,7 @@
"source": {
"source": "url",
"url": "https://github.com/qdrant/skills.git",
"sha": "82337ccd4be601e52871f101844d57b2adbac52b"
"sha": "cace39df5cc46f7f0c192ced7391d767749142a0"
},
"homepage": "https://skills.qdrant.tech"
},
@@ -2127,7 +2001,7 @@
"source": {
"source": "url",
"url": "https://github.com/quarkusio/quarkus-agent-mcp.git",
"sha": "e711107a1171507212dd0edd17b5a922212c3a97"
"sha": "01847d5d2eca02bc5751cce18deb41ad76a7a873"
},
"homepage": "https://quarkus.io"
},
@@ -2140,7 +2014,7 @@
"url": "https://github.com/railwayapp/railway-skills.git",
"path": "plugins/railway",
"ref": "main",
"sha": "1df604ebd18f528ff16b84975125ecff944cc036"
"sha": "831130cda8a659e8c47addd28be2744e9e67d31c"
},
"homepage": "https://docs.railway.com/ai/claude-code-plugin"
},
@@ -2163,7 +2037,7 @@
"source": "url",
"url": "https://github.com/RevenueCat/rc-claude-code-plugin.git",
"path": "revenuecat",
"sha": "473fd504bf13d25e76bf4a0267b42be3794f6266"
"sha": "b34f9bebe02ceb7e3f32e6d7d081cdfb2e7c37a6"
},
"homepage": "https://www.revenuecat.com"
},
@@ -2203,7 +2077,7 @@
"source": {
"source": "url",
"url": "https://github.com/resend/resend-skills.git",
"sha": "0888546d6a69149c8d2402d46f395f5dddb1c720"
"sha": "0f598ef55623e37a76f972e93a53ffa91c1dc9d1"
},
"homepage": "https://resend.com"
},
@@ -2215,7 +2089,7 @@
"source": "url",
"url": "https://github.com/RevenueCat/rc-claude-code-plugin.git",
"path": "revenuecat",
"sha": "473fd504bf13d25e76bf4a0267b42be3794f6266"
"sha": "b34f9bebe02ceb7e3f32e6d7d081cdfb2e7c37a6"
},
"homepage": "https://www.revenuecat.com"
},
@@ -2314,7 +2188,7 @@
"source": {
"source": "url",
"url": "https://github.com/sanity-io/agent-toolkit.git",
"sha": "66f0ec5d9167b3ccb8b3450e5ec34f3b523d4139"
"sha": "7e04973754975e73b306b1d4dbae561160d797e9"
},
"homepage": "https://www.sanity.io"
},
@@ -2348,7 +2222,7 @@
"url": "https://github.com/SAP/open-ux-tools.git",
"path": "packages/fiori-mcp-server",
"ref": "main",
"sha": "fbfe8c32fb9fc64583aa72ac03ab64f553c407ee"
"sha": "b326a9a52b1da51effed574587e31fe5a2755b96"
},
"homepage": "https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server"
},
@@ -2380,14 +2254,14 @@
"url": "https://github.com/spotify/save-to-spotify.git",
"path": "plugin",
"ref": "main",
"sha": "cd4ea68111d96769b09c0b0d2199e692cf00a73c"
"sha": "35527660378c769bcbcfba89d8086d8b9fc4fccb"
},
"homepage": "https://github.com/spotify/save-to-spotify"
},
{
"name": "security-guidance",
"description": "Security review for Claude-generated code. Pattern-based warnings on edits, LLM-powered diff review on Stop, and an agentic commit reviewer that catches injection, XSS, SSRF, hardcoded secrets, and 25+ other vulnerability classes.",
"version": "2.0.3",
"version": "2.0.4",
"author": {
"name": "Anthropic",
"email": "support@anthropic.com"
@@ -2415,7 +2289,7 @@
"source": {
"source": "url",
"url": "https://github.com/getsentry/sentry-for-claude.git",
"sha": "030b01fb76b21f5d7ef6af5a3c3dfa658a9b5024"
"sha": "849303a8411c242d250885ffe714235a3bc2f5fe"
},
"homepage": "https://github.com/getsentry/sentry-for-claude/tree/main"
},
@@ -2431,7 +2305,7 @@
"url": "https://github.com/getsentry/cli.git",
"path": "plugins/sentry-cli",
"ref": "main",
"sha": "9e9fe0fb6444f18ed109058b2749cced3c21f87e"
"sha": "5b78ddaf28252cb514007526025b138569445fd4"
},
"homepage": "https://sentry.io"
},
@@ -2496,7 +2370,7 @@
"source": {
"source": "url",
"url": "https://github.com/Shopify/Shopify-AI-Toolkit.git",
"sha": "a8e87a7cff153479eb77230d9c232484a1f3062f"
"sha": "859be93bfc858f183ff5eb40183e35a4d91d2950"
},
"homepage": "https://shopify.dev"
},
@@ -2534,7 +2408,7 @@
"url": "https://github.com/Snowflake-Labs/snowflake-ai-kit.git",
"path": "plugins/cortex-code",
"ref": "main",
"sha": "6a22eb1ff3b451c35e40468a118bbee54610c9bd"
"sha": "c3f720020a3b6c8927f97362c2e5884e959acd53"
},
"homepage": "https://docs.snowflake.com/en/user-guide/cortex-code"
},
@@ -2574,20 +2448,6 @@
},
"homepage": "https://sourcegraph.com"
},
{
"name": "spanner",
"description": "Connect and interact with Spanner data using natural language.",
"author": {
"name": "Google LLC"
},
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/spanner.git",
"sha": "d4678e2bc04f60f3dfcdb6b916df28e63a0d615f"
},
"homepage": "https://github.com/gemini-cli-extensions/spanner"
},
{
"name": "spotify-ads-api",
"description": "Manage Spotify ad campaigns with natural language. Create campaigns, ad sets, ads, pull reports, and handle OAuth — all through conversation.",
@@ -2608,7 +2468,7 @@
"url": "https://github.com/stripe/ai.git",
"path": "providers/claude/plugin",
"ref": "main",
"sha": "b8f6adcb5d05f6ff01334411561ee8cb1ec014c6"
"sha": "e27ee0091ed20f7557f3241e00ade3d4846af9d6"
},
"homepage": "https://github.com/stripe/ai/tree/main/providers/claude/plugin"
},
@@ -2631,7 +2491,7 @@
"source": {
"source": "url",
"url": "https://github.com/supabase-community/supabase-plugin.git",
"sha": "2ed49769b1ec2f6703a14290af484df651336150"
"sha": "3217ac038647f6901a166f3264a32f01833f73ba"
},
"homepage": "https://github.com/supabase-community/supabase-plugin"
},
@@ -2676,7 +2536,7 @@
"source": {
"source": "url",
"url": "https://github.com/JetBrains/teamcity-cli.git",
"sha": "67e21f0be908daa7ca1e04c8016d1bc81750baee"
"sha": "3cc3013c0f8106ffc845b34fb322d763803bcb0e"
},
"homepage": "https://www.jetbrains.com/teamcity/"
},
@@ -2707,7 +2567,7 @@
"source": {
"source": "url",
"url": "https://github.com/togethercomputer/skills.git",
"sha": "fb94cc1402900eb608c31e7102fc23566f8b0363"
"sha": "9772f2a2f83e2184c341dd2650ac4c7efb76c33b"
},
"homepage": "https://www.together.ai"
},
@@ -2769,7 +2629,7 @@
"url": "https://github.com/UI5/plugins-coding-agents.git",
"path": "plugins/ui5",
"ref": "main",
"sha": "9b3d7d80356f687725f9584988e4038dbead0d53"
"sha": "767ac53cb056a0c900374ccea0df96c54b769eb2"
},
"homepage": "https://github.com/UI5/plugins-coding-agents"
},
@@ -2787,7 +2647,7 @@
"url": "https://github.com/UI5/plugins-coding-agents.git",
"path": "plugins/ui5-typescript-conversion",
"ref": "main",
"sha": "9b3d7d80356f687725f9584988e4038dbead0d53"
"sha": "767ac53cb056a0c900374ccea0df96c54b769eb2"
},
"homepage": "https://github.com/UI5/plugins-coding-agents"
},
@@ -2803,7 +2663,7 @@
"url": "https://github.com/val-town/plugins.git",
"path": "plugin",
"ref": "main",
"sha": "02631f998eda9b88d73d699703b062db059d506b"
"sha": "e01069e11ea6e46b8d2d5fd2945f2dd4d33e6a57"
},
"homepage": "https://val.town"
},
@@ -2856,7 +2716,7 @@
"source": {
"source": "url",
"url": "https://github.com/explorium-ai/vibeprospecting-plugin.git",
"sha": "aa5903f52d79e7f2a5f9c324c6fff7d5a5d92631"
"sha": "7ed0c4e2965ee315132c3c714609b46b23b5edc0"
},
"homepage": "https://www.vibeprospecting.ai/product/claude-plugin"
},
@@ -2881,7 +2741,7 @@
"source": {
"source": "url",
"url": "https://github.com/wix/skills.git",
"sha": "188ed338f39d70e5aef7f9a2582bbf338f223b78"
"sha": "f99715fc149208608a148c0fe0ed16c0f80ee734"
},
"homepage": "https://dev.wix.com/docs/wix-cli/guides/development/about-wix-skills"
},
@@ -2934,7 +2794,7 @@
"url": "https://github.com/zapier/zapier-mcp.git",
"path": "plugins/zapier",
"ref": "main",
"sha": "770167c572deaf74c588b45d88003ddf2145d608"
"sha": "f34a7854febed415c9ef766eec1c66529ef0668e"
},
"homepage": "https://github.com/zapier/zapier-mcp/tree/main/plugins/zapier"
},
@@ -2988,7 +2848,7 @@
"source": {
"source": "url",
"url": "https://github.com/zscaler/zscaler-mcp-server.git",
"sha": "f84ce4f0ed48047614a4202ac311cbdf00ea9a10"
"sha": "be37fb604a07dc9c5a4c3e009312c4f11acaa6d3"
},
"homepage": "https://github.com/zscaler/zscaler-mcp-server"
}

View File

@@ -24,10 +24,6 @@ mkdir -p legacy && ln -s /path/to/your/legacy/codebase legacy/billing
`/modernize-assess` works best with [`scc`](https://github.com/boyter/scc) (LOC + complexity + COCOMO) or [`cloc`](https://github.com/AlDanial/cloc), and falls back to `find`/`wc` if neither is installed. Portfolio mode also benefits from [`lizard`](https://github.com/terryyin/lizard) (cyclomatic complexity). The commands degrade gracefully without them, but the metrics will be coarser.
## Secret handling
Legacy systems routinely contain live credentials, and assessment artifacts get committed and shared. **Every agent in this plugin masks credential values** — findings, rule-card parameters, architecture notes, and test fixtures cite `file:line` with a masked preview (`AKIA****`), never the value. When credentials are found, a per-credential inventory (type, location, blast radius, rotation recommendation) is written to `analysis/<system>/SECRETS.local.md`, which the commands gitignore before writing; on non-git projects the quarantine file goes to `~/.modernize/<system>/` instead. `/modernize-harden` splits its remediation diff so credential-removal hunks (which necessarily contain the raw value) land in a gitignored `security_remediation.local.patch`, never the shareable patch. Pass `--show-secrets` to include raw values in the quarantine file (and only there). If you ran an earlier version of this plugin on a real system, check whether `analysis/` artifacts containing credentials were committed or shared, and rotate anything that was.
## Commands
The commands are designed to be run in order, but each produces a standalone artifact so you can stop, review, and resume.

View File

@@ -29,12 +29,6 @@ For **transformed code**:
- Does the test suite actually pin behavior, or just exercise code paths?
- What would the on-call engineer need at 3am that isn't here?
## Secret handling (mandatory)
When a finding quotes code containing a credential, key, token, or
connection string, mask the value (`'Pr0d****'`) and cite `file:line`
findings get appended verbatim to committed notes files.
## Output
Findings ranked **Blocker / High / Medium / Nit**. Each with: what, where,

View File

@@ -40,15 +40,6 @@ of the technology, skip it.
from structure/names), **Low** (ambiguous; needs SME).
6. If confidence < High, write the exact question an SME must answer.
## Secret handling (mandatory)
Rule parameters sometimes *are* credentials — hardcoded passwords in auth
checks, API keys in partner-service calls, connection strings in batch
routines. Record the **rule**, never the **value**: write the parameter as
`<credential — masked, see file:line>` with at most a 24 character
preview. Rule cards flow into briefs and steering decks; a raw credential
in a parameter list is a leak.
## Output format
One "Rule Card" per rule (see the format in the `/modernize-extract-rules`

View File

@@ -32,15 +32,6 @@ and explain it in terms a modern engineer can act on.
- **Note what's missing.** Unhandled error paths, TODO comments, commented-out
blocks, magic numbers — these are signals about history and risk.
## Secret handling (mandatory)
Legacy code is full of live credentials, and your findings get copied into
shareable reports. When the evidence for a finding — hardcoded config,
dead code, debt, an interface payload — includes a credential, API key,
token, connection string, or private key, **never reproduce the value**.
Cite `file:line` with a masked preview (`VALUE 'Pr0d****'`,
`password=****`). The finding is the practice, not the value.
## Output format
Default to structured markdown: tables for inventories, Mermaid for graphs,

View File

@@ -39,30 +39,7 @@ terminal/screen items don't apply to a SPA. Work through what's relevant:
Use available SAST where it helps (npm audit, pip-audit, grep for known-bad
patterns) but **read the code** — tools miss logic flaws. Show tool output
verbatim — except secret values, which you redact (see below) — then add
your manual findings.
## Secret handling (mandatory)
Legacy codebases routinely contain live production credentials, and your
findings get pasted into decks, tickets, and committed markdown. Copying a
secret into a report multiplies the exposure you were hired to find.
When you discover a hardcoded credential, API key, token, connection
string, or private key:
- **Never write the secret's value into any output** — no finding table,
no report, no quoted code excerpt, no echoed tool output. Mask it to the
first 24 identifying characters plus `****` (`AKIA****`,
`postgres://app_user:****@db-prod…`). If a scanner prints a secret,
redact it before including the excerpt.
- Cite `file:line`. The source file is the canonical location — anyone who
legitimately needs the value can open it there.
- State what the credential appears to grant access to (database, queue,
cloud account, third-party API) and whether it looks like a production
or test credential.
- Recommend rotation for anything that looks live — exposure in source
means it is already compromised, independent of any modernization plan.
verbatim, then add your manual findings.
## Reporting standard

View File

@@ -28,15 +28,6 @@ someone thinks it should do) so that a rewrite can be proven equivalent.
`@Disabled("pending RULE-NNN")` / `@pytest.mark.skip` / `it.todo()` — never
deleted.
## Secret handling (mandatory)
Never copy credential-like literals — passwords, API keys, tokens,
connection strings — from legacy code into test fixtures. Tests live in
the deliverable codebase and get committed. Substitute clearly-fake values
of the same shape and length and note the substitution in a comment.
Anything a test genuinely needs live (e.g. a real database connection for
a dual-run harness) is read from an environment variable, never inlined.
## Output
Idiomatic tests for the requested target stack (JUnit 5 / pytest / Vitest /

View File

@@ -1,13 +1,11 @@
---
description: Full discovery & portfolio analysis of a legacy system — inventory, complexity, debt, effort estimation
argument-hint: <system-dir> [--show-secrets] | --portfolio <parent-dir>
argument-hint: <system-dir> | --portfolio <parent-dir>
---
**Mode select.** If `$ARGUMENTS` starts with `--portfolio`, run **Portfolio
mode** against the directory that follows. Otherwise run **Single-system
mode** against the system dir. Parse flags positionally-independently:
`--show-secrets` may appear before or after the system dir — the system
dir is the first non-flag token.
mode** against `legacy/$1`.
---
@@ -110,16 +108,12 @@ Spawn three subagents **in parallel**:
2. **legacy-analyst** — "Identify technical debt in legacy/$1: dead code,
deprecated APIs, copy-paste duplication, god objects/programs, missing
error handling, hardcoded config. Return the top 10 findings ranked by
remediation value, each with file:line evidence. If evidence contains a
credential value, mask it per your secret-handling rules — never quote
it."
remediation value, each with file:line evidence."
3. **security-auditor** — "Scan legacy/$1 for security vulnerabilities:
injection, auth weaknesses, hardcoded secrets, vulnerable dependencies,
missing input validation. Return findings in CWE-tagged table form with
file:line evidence and severity. Mask every discovered credential value
per your secret-handling rules — file:line plus a 24 character masked
preview, never the value itself."
file:line evidence and severity."
Wait for all three. Synthesize their findings.
@@ -147,31 +141,6 @@ need explained.
## Step 6 — Write the assessment
**Secrets quarantine first.** The assessment gets shared and committed —
discovered credential values must never appear in it. If the
security-auditor found any hardcoded credentials:
1. Ensure `analysis/.gitignore` exists and contains the lines
`SECRETS.local.md` and `*.local.patch` (create or append as needed —
the patch pattern is used by `/modernize-harden`; writing both now
means the ignore set is complete from first contact). If the project is a
git repo, verify with `git check-ignore -q analysis/$1/SECRETS.local.md`
— do not write any findings until the check passes. If there is **no
git repo** (check for `.svn`/`.hg`/`CVS` too — a `.gitignore` protects
nothing under another VCS): refuse `--show-secrets` and write
`SECRETS.local.md` to `~/.modernize/$1/` instead of the project tree,
telling the user where it went and why.
2. Write `SECRETS.local.md`: one row per credential — masked preview,
`file:line`, credential type, what it grants access to,
production/test guess, rotation recommendation. Only if the user passed
`--show-secrets`, add the raw value column here — this file only, never
ASSESSMENT.md.
3. Masking applies to **every section of ASSESSMENT.md**, whichever agent
produced the finding — the Technical Debt section quotes hardcoded
config; those quotes follow the same masking rule as Security Findings.
The Security Findings section adds a one-line pointer:
"Credential inventory in SECRETS.local.md (gitignored; not for sharing)."
Create `analysis/$1/ASSESSMENT.md` with these sections:
- **Executive Summary** (3-4 sentences: what it is, how big, how risky, headline recommendation)
- **System Inventory** (the scc table + tech fingerprint)

View File

@@ -46,7 +46,7 @@ Merge the three result sets. Deduplicate. For each distinct rule, write a
When <trigger>
Then <outcome>
[And <additional outcome>]
**Parameters:** <constants, rates, thresholds with their current values — credentials masked: `<credential — masked, see file:line>`>
**Parameters:** <constants, rates, thresholds with their current values>
**Edge cases handled:** <list>
**Suspected defect:** <optional — legacy behavior that looks wrong; decide preserve-vs-fix during transform>
**Confidence:** High | Medium | Low — <why; if < High, state the exact SME question>

View File

@@ -1,42 +1,14 @@
---
description: Security vulnerability scan with a reviewable remediation patch — OWASP, CWE, CVE, secrets, injection
argument-hint: <system-dir> [--show-secrets]
argument-hint: <system-dir>
---
Run a **security hardening pass** on the legacy system: find
vulnerabilities, rank them, and produce a reviewable patch for the
critical ones. Parse arguments flag-independently: the system dir
(referred to as `$1` below) is the first non-flag token in `$ARGUMENTS`;
`--show-secrets` may appear anywhere.
Run a **security hardening pass** on `legacy/$1`: find vulnerabilities, rank
them, and produce a reviewable patch for the critical ones.
This command never edits `legacy/` — it writes findings and a proposed patch
to `analysis/$1/`. The user reviews and applies (or not).
## Step 0 — Secrets quarantine setup
Findings files get shared, committed, and pasted into decks — discovered
credential values must never land in them. Before any scanning:
1. Ensure `analysis/.gitignore` exists and contains the lines
`SECRETS.local.md` and `*.local.patch`. Create the file or append the
missing lines.
2. If the project is a git repo, verify with
`git check-ignore -q analysis/$1/SECRETS.local.md` — if that exits
non-zero, fix the ignore rule before proceeding. Do not write any
findings until this check passes.
3. **If there is no git repo** (check for `.svn`/`.hg`/`CVS` too — a
`.gitignore` protects nothing under another VCS): refuse
`--show-secrets`, and write `SECRETS.local.md` and any `.local.patch`
file to `~/.modernize/$1/` instead of the project tree, telling the
user where they went and why.
All secret values in every shareable artifact this command produces are
**masked** (`AKIA****`, `password=****`) and cited by `file:line`. Raw
values may appear in exactly two places, both gitignored: the
`*.local.patch` remediation hunks (unavoidably — see Remediate) and, only
with `--show-secrets`, `SECRETS.local.md`. Never in SECURITY_FINDINGS.md
or patch commentary.
## Scan
Spawn the **security-auditor** subagent:
@@ -48,9 +20,7 @@ hardcoded secrets, vulnerable dependency versions, missing input validation,
path traversal. For each finding return: CWE ID, severity
(Critical/High/Med/Low), file:line, one-sentence exploit scenario, and
recommended fix. Run any available SAST tooling (npm audit, pip-audit,
OWASP dependency-check) and include its raw output. Mask every discovered
credential value per your secret-handling rules — file:line plus a 24
character masked preview, never the value itself."
OWASP dependency-check) and include its raw output."
## Triage
@@ -59,50 +29,26 @@ Write `analysis/$1/SECURITY_FINDINGS.md`:
- Findings table sorted by severity
- Dependency CVE table (package, installed version, CVE, fixed version)
If any hardcoded credentials were found, also write
`analysis/$1/SECRETS.local.md` (the gitignored quarantine file from Step 0):
one row per credential — masked preview, `file:line`, credential type, what
it appears to grant access to, production/test guess, and a rotation
recommendation. With `--show-secrets`, append the raw value column here —
this file only. SECURITY_FINDINGS.md gets a one-line pointer:
"N hardcoded credentials found — inventory in SECRETS.local.md (gitignored;
not for sharing)."
## Remediate
For each **Critical** and **High** finding, draft a minimal, targeted fix.
Do **not** edit `legacy/` — write fixes as unified diffs with **paths
relative to the project root** (`legacy/$1/...`), applied from the project
root, with a comment line above each hunk citing the finding ID it
addresses (`# SEC-001: parameterize the query`).
**Credential findings split into two files.** A diff that removes a
hardcoded secret necessarily contains the raw value on its `-` and
context lines — that cannot go in the shareable patch:
- `analysis/$1/security_remediation.patch` (shareable) — every
non-credential hunk, plus for each credential finding a comment-only
placeholder: `# SEC-NNN: credential remediation — hunk in
security_remediation.local.patch (gitignored; not for sharing)`.
- `analysis/$1/security_remediation.local.patch` (gitignored in Step 0) —
the real, applyable hunks for credential findings only.
Do **not** edit `legacy/` — write all fixes as a single unified diff to
`analysis/$1/security_remediation.patch`, with a comment line above each
hunk citing the finding ID it addresses (`# SEC-001: parameterize the query`).
Add a **Remediation Log** section to SECURITY_FINDINGS.md mapping each
finding ID → one-line summary of the proposed fix and which patch file
carries the hunk.
finding ID → one-line summary of the proposed fix and the patch hunk that
implements it.
## Verify
Spawn the **security-auditor** again to **review both patches** against
the original code:
Spawn the **security-auditor** again to **review the patch** against the
original code:
"Review analysis/$1/security_remediation.patch and
analysis/$1/security_remediation.local.patch against legacy/$1. For each
"Review analysis/$1/security_remediation.patch against legacy/$1. For each
hunk: does it fully remediate the cited finding? Does it introduce new
vulnerabilities or change behavior beyond the fix? Confirm no raw
credential values appear anywhere in the shareable patch. Return one
verdict per hunk: RESOLVES / PARTIAL / INTRODUCES-RISK, with a one-line
reason."
vulnerabilities or change behavior beyond the fix? Return one verdict per
hunk: RESOLVES / PARTIAL / INTRODUCES-RISK, with a one-line reason."
Add a **Patch Review** section to SECURITY_FINDINGS.md with the verdicts.
If any hunk is PARTIAL or INTRODUCES-RISK, revise the patch and re-review.
@@ -111,12 +57,8 @@ If any hunk is PARTIAL or INTRODUCES-RISK, revise the patch and re-review.
Tell the user the artifacts are ready:
- `analysis/$1/SECURITY_FINDINGS.md` — findings, remediation log, patch review
- `analysis/$1/security_remediation.patch` — review, then apply **from the
project root**: `git apply analysis/$1/security_remediation.patch`
(if `legacy/$1` is a symlink, use `git apply --unsafe-paths` or apply
with `patch -p0` from the project root)
- `analysis/$1/security_remediation.local.patch` — the credential fixes;
apply the same way, and rotate the affected credentials regardless
- `analysis/$1/security_remediation.patch` — review, then apply if appropriate
with `git -C legacy/$1 apply ../../analysis/$1/security_remediation.patch`
- Re-run `/modernize-harden $1` after applying to confirm resolution
Suggest: `glow -p analysis/$1/SECURITY_FINDINGS.md`

View File

@@ -1,6 +1,6 @@
{
"name": "security-guidance",
"version": "2.0.3",
"version": "2.0.4",
"description": "Security review for Claude-generated code. Pattern-based warnings on edits, LLM-powered diff review on Stop, and an agentic commit reviewer that catches injection, XSS, SSRF, hardcoded secrets, and 25+ other vulnerability classes.",
"author": {
"name": "David Dworken",

View File

@@ -102,6 +102,41 @@ SDK_BOOTSTRAP_ERR_CODES = {
"_uncategorized": 99,
}
# Exception-type encoding for the "exc:<TypeName>" err_kinds (the generic
# `except Exception` path — venv/pip raised a Python exception rather than
# a CalledProcessError with categorizable stderr).
#
# #2154 telemetry surfaced that the dominant remaining venv BUILD_FAILED
# bucket (phase=venv, err=99) is ~99% `exc:` with stderr_sig=NULL — i.e.
# exceptions, not stderr-bearing subprocess failures — so the stderr_sig
# hash couldn't distinguish them. This maps the exception TYPE to a stable
# code so BQ can tell FileNotFoundError (python/venv binary missing) from
# PermissionError (read-only home) from a bare OSError, etc.
#
# All the FileNotFoundError/PermissionError/etc. entries are OSError
# subclasses, so they ALSO carry an errno (see _encode_errno) — the type
# code gives the Python class, errno gives the OS-level cause. APPEND-ONLY.
SDK_BOOTSTRAP_EXC_CODES = {
"FileNotFoundError": 1, # interpreter/venv path component missing
"PermissionError": 2, # read-only home, sandboxed FS
"NotADirectoryError": 3,
"IsADirectoryError": 4,
"FileExistsError": 5, # (sentinel race is handled separately; this
# is FileExistsError from elsewhere in venv)
"OSError": 6, # bare OSError — errno carries the real cause
"BlockingIOError": 7,
"BrokenPipeError": 8,
"ConnectionError": 9,
"TimeoutError": 10, # distinct from subprocess.TimeoutExpired
"InterruptedError": 11,
"MemoryError": 12,
"UnicodeDecodeError": 13,
"ValueError": 14,
"RuntimeError": 15,
# 1698 reserved; APPEND-ONLY.
"_other_exc": 99, # an exception type not in this map
}
def _encode_phase(s):
"""Map err_phase string to its telemetry integer code, or 0 if unset.
@@ -158,6 +193,55 @@ def _encode_stderr_sig(err_kind):
return int.from_bytes(h[:2], "big") % 1000
def _encode_exc_kind(err_kind):
"""Map an "exc:<TypeName>[:errno]" err_kind to its exception-type code
(SDK_BOOTSTRAP_EXC_CODES). Returns 0 for non-exc err_kinds (so the
sdk_bootstrap_exc field auto-omits on stderr/categorized failures).
Unmapped exception types → 99 (_other_exc)."""
if not err_kind or not err_kind.startswith("exc:"):
return 0
# "exc:OSError:28" → "OSError"; "exc:RuntimeError" → "RuntimeError"
name = err_kind[len("exc:"):].split(":", 1)[0].strip()
if not name:
return 0
return SDK_BOOTSTRAP_EXC_CODES.get(name, SDK_BOOTSTRAP_EXC_CODES["_other_exc"])
def _encode_errno(err_kind):
"""Extract the OS errno from an "exc:<TypeName>:<errno>" err_kind.
OSError-family exceptions embed their errno (ENOENT=2, EACCES=13,
ENOSPC=28, …) — the OS-level cause is far more actionable than the
Python class alone. Returns 0 when absent/non-numeric (field omitted)."""
if not err_kind or not err_kind.startswith("exc:"):
return 0
parts = err_kind.split(":")
if len(parts) < 3:
return 0
try:
return int(parts[2])
except (ValueError, IndexError):
return 0
def _probe_has_pip() -> bool:
"""True iff the current interpreter can run pip (`-m pip --version`).
Probed only on the venv_ensurepip_fail path (see __main__), NOT on the
happy path — it's an extra subprocess we only want when diagnosing a
failure. The result decides whether a `pip install --target` fallback
(Option A) is even viable for this machine: ensurepip/venv missing but
pip present → --target would work; pip also missing → it wouldn't, and
the user needs a system package (python3-venv / a complete Python)."""
try:
r = subprocess.run(
[sys.executable, "-m", "pip", "--version"],
capture_output=True, timeout=10,
)
return r.returncode == 0
except Exception:
return False
def _sdk_on_syspath() -> bool:
# find_spec is ~10ms; actually importing the SDK pulls in
# transitive deps and costs ~800ms — too heavy for a
@@ -364,6 +448,13 @@ def main() -> tuple[int, str, str]:
except subprocess.TimeoutExpired:
return BUILD_FAILED, err_phase, "subprocess_timeout"
except Exception as e:
# Embed errno for OSError-family exceptions ("exc:OSError:28") so
# telemetry can decode the OS-level cause (ENOENT/EACCES/ENOSPC/…),
# not just the Python class. #2154 follow-up: this is the dominant
# remaining venv BUILD_FAILED bucket. See _encode_exc_kind/_encode_errno.
errno = getattr(e, "errno", None)
if isinstance(errno, int):
return BUILD_FAILED, err_phase, f"exc:{type(e).__name__}:{errno}"
return BUILD_FAILED, err_phase, f"exc:{type(e).__name__}"
finally:
# Only remove the sentinel if THIS process created it. The
@@ -467,6 +558,30 @@ if __name__ == "__main__":
sig = _encode_stderr_sig(err_kind)
if sig:
metrics["sdk_bootstrap_stderr_sig"] = sig
# Exception-type + errno for the "exc:" bucket (the dominant
# remaining venv BUILD_FAILED mode per #2154 telemetry). Both
# auto-omit (0) on stderr/categorized failures.
exc = _encode_exc_kind(err_kind)
if exc:
metrics["sdk_bootstrap_exc"] = exc
exc_errno = _encode_errno(err_kind)
if exc_errno:
metrics["sdk_bootstrap_errno"] = exc_errno
# venv_ensurepip_fail (code 11) is the top categorizable venv
# failure, and telemetry shows it's NOT just Debian — macOS has the
# most distinct affected users. Probe whether this interpreter has
# pip so we know if a `pip install --target` fallback (Option A)
# would actually help, vs the user needing a system package. Probed
# only here (not on the happy path) to avoid an extra subprocess
# per healthy session.
if _encode_err_kind(err_kind) == 11:
metrics["sdk_has_pip"] = _probe_has_pip()
# Interpreter version (major*100 + minor, e.g. 309 / 312), emitted on
# every bootstrap. Disambiguates the macOS cohort (Apple 3.9 vs a 3.10+
# with broken ensurepip) for both venv_ensurepip_fail AND
# HOOK_PY_INCOMPATIBLE (whose "py_3.9" err_kind otherwise collapses to
# err=99, losing the version). Cheap — no subprocess, just sys.version_info.
metrics["sdk_hook_py"] = sys.version_info[0] * 100 + sys.version_info[1]
pv = _plugin_version_int()
if pv:
metrics["pv"] = pv