diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09d808463..b84699717 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -288,6 +288,15 @@ new watcher for subsequent subscriptions because closing is terminal. Its Node.js 22.22 minimum is covered by the supported Node.js `>=24.22.1` runtime. Regenerate `bun.lock` with `bun install` after updating the exact pin. +Keep `@sentry/node` pinned exactly to `11.9.0` and regenerate `bun.lock` with +`bun install` after updating it. The optional reporter uses a lazy ESM import +after trust is established. Keep default integrations and tracing disabled, +set `attachStacktrace: false`, and explicitly disable `dataCollection` categories +so only vetted messages are reported. Do not add automatic instrumentation or +an OpenTelemetry provider to this path. Run `bun test src/utils/sentry.test.ts` +when updating the SDK or initialization. Its Node.js minimum is covered by the +supported runtime; self-hosted Sentry requires 26.4.2 or later. + The root `overrides` block also controls transitive resolutions: - `ip-address`: `10.7.4` sets the security floor for the MCP SDK's diff --git a/bun.lock b/bun.lock index 9f72e3ec0..e77ea87cb 100644 --- a/bun.lock +++ b/bun.lock @@ -23,7 +23,7 @@ "@grpc/grpc-js": "1.14.6", "@grpc/proto-loader": "0.8.1", "@modelcontextprotocol/sdk": "1.32.1", - "@sentry/node": "10.70.0", + "@sentry/node": "11.9.0", "@smithy/core": "3.36.2", "@smithy/node-http-handler": "4.12.1", "@types/bun": "1.4.4", @@ -121,12 +121,6 @@ "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.137.0", "", { "dependencies": { "@opentelemetry/api": "^1.4.1", "json-schema-to-ts": "^3.1.1", "standardwebhooks": "^1.0.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-YpEnbPz8kWF1BWthwUCnRhci7OvDLSUO8NFKoevoPkjNkK9CJeibZITgxr9vhqOP8Le61e5tNDHFTxw1dc6Png=="], - "@apm-js-collab/code-transformer": ["@apm-js-collab/code-transformer@0.18.1", "", { "dependencies": { "@types/estree": "^1.0.8", "astring": "^1.9.0", "esquery": "^1.7.0", "meriyah": "^6.1.4", "semifies": "^1.0.0", "source-map": "^0.6.0" }, "bin": { "code-transformer": "cli.js" } }, "sha512-u1Hb6bHjWtkSpiprwVP6YaHC1DTN4RAU3zYkUDUe7WMnJwdyU1pwTL9dFKiSJB9IiLue/EQovmyx6xhU7FFtAQ=="], - - "@apm-js-collab/code-transformer-bundler-plugins": ["@apm-js-collab/code-transformer-bundler-plugins@0.7.4", "", { "dependencies": { "@apm-js-collab/code-transformer": "^0.18.1", "es-module-lexer": "^2.1.0", "magic-string": "^0.30.21", "module-details-from-path": "^1.0.4" } }, "sha512-nAfOeZPSUAQvJa1iFT/5oCrTm5YQhMMrfCNthNnaXHZiOQhu1KGuLoIx7HtbAi3wfwaBYLaICPIeenIaEwcXIg=="], - - "@apm-js-collab/tracing-hooks": ["@apm-js-collab/tracing-hooks@0.13.0", "", { "dependencies": { "@apm-js-collab/code-transformer": "^0.18.0", "debug": "^4.4.1", "module-details-from-path": "^1.0.4" } }, "sha512-mTvWz9rnQwx1U3h0XPTHaX7bgfkpipLLTQyjlC2cdhQpQEuoLT0AGzoydeoq2NxfEVv6fWOOETcSbb2nptleyw=="], - "@aws-crypto/crc32": ["@aws-crypto/crc32@3.0.0", "", { "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" } }, "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA=="], "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], @@ -345,20 +339,6 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], - "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.220.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w=="], - - "@opentelemetry/core": ["@opentelemetry/core@2.10.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ=="], - - "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.220.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.220.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw=="], - - "@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], - - "@opentelemetry/sdk-trace": ["@opentelemetry/sdk-trace@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ=="], - - "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ=="], - - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], - "@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="], "@orama/plugin-data-persistence": ["@orama/plugin-data-persistence@3.1.18", "", { "dependencies": { "@msgpack/msgpack": "^3.1.2", "@orama/orama": "3.1.18", "dpack": "^0.6.22", "seqproto": "^0.2.3" } }, "sha512-pfBbpK96VRW/7IkdMHn2HaW3/+4k2C9Uwyup0IONNuz5bG3L1orCNFZPBmu+zcokOU2YH+IAVuQz6MlvqOe3iw=="], @@ -465,17 +445,19 @@ "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], - "@sentry/conventions": ["@sentry/conventions@0.16.0", "", {}, "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ=="], + "@sentry/bundler-plugins": ["@sentry/bundler-plugins@11.9.0", "", { "dependencies": { "@sentry/core": "11.9.0", "dotenv": "^17.4.2", "glob": "^13.0.6", "magic-string": "~0.30.8 || ^1.0.0", "oxc-parser": "^0.152.0", "sentry": "^0.45.0", "supports-color": "^8.1.1" }, "peerDependencies": { "rollup": ">=3.2.0", "webpack": ">=5.1.0" }, "optionalPeers": ["rollup", "webpack"] }, "sha512-wpz/gcM/6rld/wb4Mhf8kbDN9ngG5myKQSZC+45gzqR1w+GcdfVnYkp3sHL9AV4mrwL8skhTItdUghMwMTknKg=="], - "@sentry/core": ["@sentry/core@10.70.0", "", { "dependencies": { "@sentry/conventions": "^0.16.0" } }, "sha512-ozhCTDqg89oB4XmWfAwuHshABpvT7AkRpaPnogopPfMAaI61G1t8EKCJ4W7aum8JSBonlfyjPCyW5oYZFm0KvA=="], + "@sentry/conventions": ["@sentry/conventions@0.27.0", "", {}, "sha512-Mrqd/o7n/z7qWSYfQQA7x4fFTA0IqOG6JNsbBpnC97khPwZk7n6DPtOO7YgaYplSl8R8QLRbBz9Xv2lVhToSAg=="], - "@sentry/node": ["@sentry/node@10.70.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.1", "@opentelemetry/instrumentation": "^0.220.0", "@opentelemetry/sdk-trace-base": "^2.9.0", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.70.0", "@sentry/node-core": "10.70.0", "@sentry/opentelemetry": "10.70.0", "@sentry/server-utils": "10.70.0", "import-in-the-middle": "^3.0.0" } }, "sha512-SPOOVxmKTVIEtqvOKkQT163e/pOwucjS7OPsCHyRs8sFR4nfBNu0EThplyqnvqd5BWBMTPH6WTBQfo+QWHV+HA=="], + "@sentry/core": ["@sentry/core@11.9.0", "", { "dependencies": { "@sentry/conventions": "^0.27.0" } }, "sha512-t0dZyHILt2XVC4Fe9UmAty6YRfOuQv95cvWeNl+HlW4LphjF5/jkALV3cJCUMdhpblYELgIJC4U/S/GoYuI2bA=="], - "@sentry/node-core": ["@sentry/node-core@10.70.0", "", { "dependencies": { "@sentry/conventions": "^0.16.0", "@sentry/core": "10.70.0", "@sentry/opentelemetry": "10.70.0", "import-in-the-middle": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/core", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/instrumentation", "@opentelemetry/sdk-trace-base"] }, "sha512-oPOEVVNxv5WHtckx2i06Wi9FLWyvOg/1DUeX732jZ4iqT2nupINaMH4nF4f4kSvUThFnxkFSRQxwqOxgzMKhKA=="], + "@sentry/node": ["@sentry/node@11.9.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.1", "@sentry/bundler-plugins": "11.9.0", "@sentry/conventions": "^0.27.0", "@sentry/core": "11.9.0", "@sentry/opentelemetry": "11.9.0", "@sentry/server-runtime-injection": "11.9.0", "@sentry/server-utils": "11.9.0" } }, "sha512-cz219PzVaAPTlQ4bRBTRBCOvxbZkXK0KkqyqWPV6OmSn6vWn935CTfOs+MKUc1A+locvB8i9OfBIbO6v03BBuQ=="], - "@sentry/opentelemetry": ["@sentry/opentelemetry@10.70.0", "", { "dependencies": { "@sentry/conventions": "^0.16.0", "@sentry/core": "10.70.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" } }, "sha512-UNV/2tqypcUK6FDzerAsFJn1Km/c4VZCYkUZDNbnV5S0cwAq2BYKMo4M5vovaLDBQlxA+Wk9ovbxi5wYjjl9fw=="], + "@sentry/opentelemetry": ["@sentry/opentelemetry@11.9.0", "", { "dependencies": { "@sentry/conventions": "^0.27.0", "@sentry/core": "11.9.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-FMCkAbEfBpFterrkR7k4P8LEabUAEHjZMbu+ycDjbnI1U4pT22tpwcFJOt06orFkacoWNsYT9+EdfmkvaCKtSA=="], - "@sentry/server-utils": ["@sentry/server-utils@10.70.0", "", { "dependencies": { "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.3", "@apm-js-collab/tracing-hooks": "^0.13.0", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.70.0", "meriyah": "^6.1.4" } }, "sha512-rzegZjMFFgCp3o+N8+XU13rfSvz4B+f8rU0ijBGrQcHdMNyfsFDTu1UTm262JofmrV2u+s+D0u0vFTnqtOGkbA=="], + "@sentry/server-runtime-injection": ["@sentry/server-runtime-injection@11.9.0", "", { "dependencies": { "@sentry/core": "11.9.0", "@sentry/server-utils": "11.9.0" } }, "sha512-Q2G5xaeoiYwGheCZyeqZ/v//SZYjRLlITlanTsZM/fox1qk7cmbDX7+o4q+JkApVV9VuXQZVxppaVCsBglfDKA=="], + + "@sentry/server-utils": ["@sentry/server-utils@11.9.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.1", "@sentry/conventions": "^0.27.0", "@sentry/core": "11.9.0" } }, "sha512-xHETix5jAjs/4tY+b9RCejEU7O49pNBiGgCWX0KFRMSpWbw9moqQ8i8N62bS7L+/BJnBcZ5SZ1nweXh7w787Qg=="], "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], @@ -619,8 +601,6 @@ "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], @@ -661,8 +641,6 @@ "chokidar": ["chokidar@6.0.0", "", { "dependencies": { "readdirp": "^5.1.1" } }, "sha512-Ec2Z7wpcKBiMpR7DxteoqS/sq5G5d2A4fpxzPv6+fD7xNNdrTghn7L11YwV5EN1B3i72Erc3XVT5FkWIDx90+A=="], - "cjs-module-lexer": ["cjs-module-lexer@2.2.1", "", {}, "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q=="], - "cli-boxes": ["cli-boxes@4.0.1", "", {}, "sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], @@ -715,6 +693,8 @@ "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], + "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "dpack": ["dpack@0.6.22", "", {}, "sha512-WGPNlW2OAE7Bj0eODMpAHUcEqxrlg01e9OFZDxQodminIgC194/cRHT7K04Z1j7AUEWTeeplYGrIv/xRdwU9Hg=="], "duck-duck-scrape": ["duck-duck-scrape@2.2.7", "", { "dependencies": { "html-entities": "^2.3.3", "needle": "^3.2.0" } }, "sha512-BEcglwnfx5puJl90KQfX+Q2q5vCguqyMpZcSRPBWk8OY55qWwV93+E+7DbIkrGDW4qkqPfUvtOUdi0lXz6lEMQ=="], @@ -735,8 +715,6 @@ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], @@ -841,6 +819,8 @@ "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "google-auth-library": ["google-auth-library@11.3.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^8.3.0", "gcp-metadata": "^9.0.0", "google-logging-utils": "^2.0.0", "jws": "^4.0.0" } }, "sha512-mQS0R2Ev/DKkzuWSYtDeq15uhDeaWuT2qzLYqJ41LmooxVKHmn5ut3pc6dlHY6wfHW/D7FBmOYjAtkjaD1A7qA=="], @@ -893,8 +873,6 @@ "ignore": ["ignore@7.0.13", "", {}, "sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q=="], - "import-in-the-middle": ["import-in-the-middle@3.3.3", "", { "dependencies": { "cjs-module-lexer": "^2.2.0", "es-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA=="], - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], @@ -997,17 +975,15 @@ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - "meriyah": ["meriyah@6.1.4", "", {}, "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ=="], - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - "mnemonist": ["mnemonist@0.39.8", "", { "dependencies": { "obliterator": "^2.0.1" } }, "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], + "mnemonist": ["mnemonist@0.39.8", "", { "dependencies": { "obliterator": "^2.0.1" } }, "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -1061,6 +1037,8 @@ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], @@ -1109,8 +1087,6 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - "require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="], - "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], @@ -1129,12 +1105,12 @@ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "semifies": ["semifies@1.0.0", "", {}, "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw=="], - "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + "sentry": ["sentry@0.45.0", "", { "bin": { "sentry": "dist/bin.cjs" } }, "sha512-kgmEbLlC22lL7uneeUm9Sjz65rp5cvwYOMTxXbZCmBum4sbVre/lLT3zTV3kwXHJ9/BM8ZS0/GhZe9Cl1t6WyA=="], + "seqproto": ["seqproto@0.2.3", "", {}, "sha512-HpNyPYl7DJa2a6XQJ+MJAc6ft6Y9ZU+zRiuvTHFpLPeqvapcTAGFJyhMEIN7Y7VXhWT6NEdNVhbXFHwtaCOfCw=="], "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], @@ -1163,8 +1139,6 @@ "smol-toml": ["smol-toml@1.9.1", "", {}, "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "standardwebhooks": ["standardwebhooks@1.1.1", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], @@ -1437,6 +1411,10 @@ "@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + "@sentry/bundler-plugins/oxc-parser": ["oxc-parser@0.152.0", "", { "dependencies": { "@oxc-project/types": "^0.152.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.152.0", "@oxc-parser/binding-android-arm64": "0.152.0", "@oxc-parser/binding-darwin-arm64": "0.152.0", "@oxc-parser/binding-darwin-x64": "0.152.0", "@oxc-parser/binding-freebsd-x64": "0.152.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.152.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.152.0", "@oxc-parser/binding-linux-arm64-gnu": "0.152.0", "@oxc-parser/binding-linux-arm64-musl": "0.152.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.152.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.152.0", "@oxc-parser/binding-linux-riscv64-musl": "0.152.0", "@oxc-parser/binding-linux-s390x-gnu": "0.152.0", "@oxc-parser/binding-linux-x64-gnu": "0.152.0", "@oxc-parser/binding-linux-x64-musl": "0.152.0", "@oxc-parser/binding-openharmony-arm64": "0.152.0", "@oxc-parser/binding-win32-arm64-msvc": "0.152.0", "@oxc-parser/binding-win32-ia32-msvc": "0.152.0", "@oxc-parser/binding-win32-x64-msvc": "0.152.0" } }, "sha512-ZztsZgaCWmPeKYMdoTT5e0oV/oxD1NeLWNR3kAtr/G707cUPYaaozRlVDHLCGnszHJRszN9mJwmPNpqgwloC0A=="], + + "@sentry/bundler-plugins/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + "@smithy/core/@smithy/types": ["@smithy/types@4.19.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q=="], "@smithy/credential-provider-imds/@smithy/types": ["@smithy/types@4.19.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q=="], @@ -1539,6 +1517,48 @@ "@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.7.4", "", { "dependencies": { "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ=="], + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.152.0", "", { "os": "android", "cpu": "arm" }, "sha512-393VtirINkae4XHBTkq/yQxhOwEr4V+r0et37P2fp+sYZSdYgZTRDPo5Um8vBh0QjMZ+RmaMy4PP0qwr4cTMwA=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.152.0", "", { "os": "android", "cpu": "arm64" }, "sha512-iYFcExATJNZ0rZj0o02THbD1Nc9jAW0nVgIQPee6ejbSASpbZXlIMw8jxtpzaG14JQTWwGSZ6U5dEtRJXE6V3Q=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.152.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fnU+DfUzi5/rFy0LzmipYRz06PAUUkOS8kCJQ3Eg6oqlunC1XZXoffFqxmdL51RmcbYtUG3P8DVG1U5fckoa2A=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.152.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Y3U2okryWTs7hDld6UhjJwLNf8/do+x4g4C+1U5SiGwrTLwbO83WJYJA8+l9iFSilWBgnfpd/+U76tfeNxUHzA=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.152.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-XkhkRrrPffEw+ZLv/d/5pEni0buxinm/E/N6fLbJCunD1DBbeCTtVP23ESV+4CWCWdSz397asks1ViGslpmWiQ=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.152.0", "", { "os": "linux", "cpu": "arm" }, "sha512-MLzsRRbdfmdzo6JEhbC1yKU9ItfvM8tEf18HM32jCiNYZEz6J3bRBRl3cTkKR0vBdrk9ZVlcARXtwHccs+epDA=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.152.0", "", { "os": "linux", "cpu": "arm" }, "sha512-HHerTEJz7Iy10Ji6BGsk4gqG6PU0UzFuBu91jIEOX89FnrI5ECWSEr7BLpkYQhp7U0QFtMnBhNfQKI0hYZK78w=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.152.0", "", { "os": "linux", "cpu": "arm64", "libc": "glibc" }, "sha512-9Bb8GyzZ2uSLzRDZ27usaKokr07mxiBe2dDF5b6bd2janmaJTT1gL51UfP7AU30yFiXrJvcRyFKREAgKwZYs0w=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.152.0", "", { "os": "linux", "cpu": "arm64", "libc": "musl" }, "sha512-om7iDMUroI74A/lfRRWY9/Ev3VWrVsrUZ4tyvyh8raBey/sdcfyAD7tYWc+iykAiotnmftyKTpUKFr1QnaBrAQ=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.152.0", "", { "os": "linux", "cpu": "ppc64", "libc": "glibc" }, "sha512-Lf1OL2lMTqwDMFC6jnRb30B+WYHKV0XT+bPHcFM6xHtNVwxpobiTzUIvKH6a0LWpvfwyru8Yu6zCGHqqFHH+gg=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.152.0", "", { "os": "linux", "cpu": "none", "libc": "glibc" }, "sha512-BYWSMQK+YJ9ZILStPcmmiSVq8KEG9Qdq/c4BHPC3rUEq/fEDCVjUIwdj5ZzjgZ9P9is9PKu0tcAXzq1ZXg4urQ=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.152.0", "", { "os": "linux", "cpu": "none", "libc": "musl" }, "sha512-d2LqHNNNqdH185sRK5dPKWZu201JrNu9/CRFuVEuT+ht7n/QaVblEMWT9MALQPrDWYvhG10P+NSKJFquDWZ09w=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.152.0", "", { "os": "linux", "cpu": "s390x", "libc": "glibc" }, "sha512-8gBeHt5rA/8+pKhHl+zLggfa3L/V/L2at96p7ty6pBE7id6lFO19nFUc15u2PcjC4OYeYK465MKy1fhhbpFIGw=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.152.0", "", { "os": "linux", "cpu": "x64", "libc": "glibc" }, "sha512-ltgrSF6L+yPTVsIdaAyqKq3zTqzj/1VlNbxWZuxAe8flR3sWdyNsKvdXukoO8/ucJbLickhmieRBge+o/e8Ugw=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.152.0", "", { "os": "linux", "cpu": "x64", "libc": "musl" }, "sha512-LcMp6BvrBnewSfdkLHoSqidhoAq4syKOTBV5TYHWd+2VnGkfD/nPRRVIzTBt/Lkbt7pfOFDGtkiTntFS0p6sQQ=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.152.0", "", { "os": "none", "cpu": "arm64" }, "sha512-jbhoMCslVxzRCRNQ8TSHxjYdSKRvk8O6ciLVDkahJvmyHDzBqba/bMQHM6ULKj/rgHNNbSzlIiakdx8SEfkJzA=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.152.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-JZYVlhv36mr/nertnRB7vchJECOBb1Lz6lltalILsB+o9M/is+H6BEDRLQKdqA4JKs6NjJszo46+iM5sfwj6jQ=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.152.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-1X9oganvVsunRH0lH10UU05N+H9atpX6WsOybByUjwnfcd3h/0tYwvuv16XuHglrIUOnxV2TodbLVEwwafd64g=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.152.0", "", { "os": "win32", "cpu": "x64" }, "sha512-sw0M80/dn9rcpu+Rqqph/C+MjvDDce6MlXmFjsNQtPEymha0eJy3ml6+foSUTqFfpDIDyHZZ4eoJ43IoMe8bJw=="], + + "@sentry/bundler-plugins/oxc-parser/@oxc-project/types": ["@oxc-project/types@0.152.0", "", {}, "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw=="], + + "@sentry/bundler-plugins/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "@typespec/ts-http-runtime/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index cf300c132..a6d15b2e9 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -407,7 +407,7 @@ missing. Install only what you need: | Azure Foundry | `CLAUDE_CODE_USE_FOUNDRY=1` | `npm i -g @anthropic-ai/foundry-sdk @azure/identity` | | Claude on Vertex AI / Gemini ADC | `CLAUDE_CODE_USE_VERTEX=1` / Gemini ADC auth | `npm i -g google-auth-library` | | Reading/processing images (clipboard paste, drag-and-drop paths, FileReadTool local decode) | local image decode — **not** pre-encoded URL/base64 passthrough | `npm i -g sharp` (intentionally opt-in; prebuilt releases load it externally only when processing requires it) | -| Optional error reporting | `SENTRY_DSN` is set | `npm i -g @sentry/node`. Without this package installed, setting `SENTRY_DSN` has no effect and reporting is silently disabled. | +| Optional error reporting | `SENTRY_DSN` is set | `npm i -g @sentry/node@11.9.0`. Without this package installed, setting `SENTRY_DSN` has no effect and reporting is silently disabled. | When installing OpenClaude from source (`bun install`), all of these are already present as dev dependencies, so source/dev builds need no extra steps. @@ -693,6 +693,8 @@ Notes: - Reporting is skipped even when `SENTRY_DSN` is set if `DISABLE_TELEMETRY` or `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` is set, matching the same privacy levels used for existing telemetry (see [Runtime Hardening](#runtime-hardening)). +- Automatic integrations, tracing, message stack traces, and SDK data collection + are disabled. Self-hosted Sentry requires version 26.4.2 or later. - Only sanitized, telemetry-safe error messages are sent — never raw error messages, which may contain file paths or other identifying information. - `@sentry/node` is an optional dev dependency and is **not included** in the @@ -702,7 +704,7 @@ Notes: disabled (no error, no crash). Install it explicitly with: ```bash - npm i -g @sentry/node + npm i -g @sentry/node@11.9.0 ``` Source builds (`bun install`) already include it as a dev dependency, so no diff --git a/package.json b/package.json index 646e429c6..2f84dd1f5 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "@grpc/grpc-js": "1.14.6", "@grpc/proto-loader": "0.8.1", "@modelcontextprotocol/sdk": "1.32.1", - "@sentry/node": "10.70.0", + "@sentry/node": "11.9.0", "@smithy/core": "3.36.2", "@smithy/node-http-handler": "4.12.1", "@types/bun": "1.4.4", diff --git a/src/test/fixtures/sentryShutdown.fixture.ts b/src/test/fixtures/sentryShutdown.fixture.ts new file mode 100644 index 000000000..de012814e --- /dev/null +++ b/src/test/fixtures/sentryShutdown.fixture.ts @@ -0,0 +1,166 @@ +import assert from 'node:assert/strict' +import { jest, spyOn } from 'bun:test' +import * as Sentry from '@sentry/node' +import { snapshotEnv } from '../envSnapshot.js' +import { + getPendingShutdownForTesting, + gracefulShutdown, + gracefulShutdownSync, + resetShutdownState, +} from '../../utils/gracefulShutdown.js' +import { initializeSentry } from '../../utils/sentry.js' +import { registerCleanup } from '../../utils/cleanupRegistry.js' +import { sleep } from '../../utils/sleep.js' + +const mode = process.argv[2] +const restoreEnv = snapshotEnv([ + 'NODE_ENV', + 'SENTRY_DSN', + 'DISABLE_TELEMETRY', + 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC', + 'CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS', +], { clear: true }) +const originalExit = process.exit +const exitError = new Error('mocked process exit') +let exitCode: number | undefined +let flushCalls = 0 +let flushTimeout: number | undefined +let flushed = false +let flushedBeforeExit = false +let unregisterCleanup: (() => void) | undefined +let restoreHooks: (() => void) | undefined +let sessionEndHooksStarted = false +let notifyExit!: () => void +const exitObserved = new Promise(resolve => { notifyExit = resolve }) +let notifyFlushStarted!: () => void +const flushStarted = new Promise(resolve => { notifyFlushStarted = resolve }) +let releaseFlush!: () => void +const pendingFlush = new Promise(resolve => { + releaseFlush = () => { + flushed = true + resolve(true) + } +}) + +try { + process.env.NODE_ENV = 'test' + process.env.SENTRY_DSN = 'https://example@o0.ingest.sentry.io/0' + await initializeSentry() + const client = Sentry.getClient() + assert(client, 'SDK was not initialized') + // Keep the real SDK and application modules, replacing only the flush I/O. + // No events are sent to the network in this fixture. + client.flush = timeout => { + flushCalls++ + flushTimeout = timeout + if (mode === 'enabled' || mode === 'blocked') jest.useFakeTimers() + notifyFlushStarted() + if (mode === 'throws') throw new Error('flush threw') + if (mode === 'rejects') return Promise.reject(new Error('flush rejected')) + return pendingFlush + } + if (mode === 'dsn-unset') delete process.env.SENTRY_DSN + if (mode === 'telemetry-disabled') process.env.DISABLE_TELEMETRY = '1' + if (mode === 'nonessential-disabled') { + process.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1' + } + if (mode === 'default-budget') { + const hooks = await import('../../utils/hooks.js') + assert.equal(hooks.getSessionEndHookTimeoutMs(), 1500) + // Consume the real cleanup and default hook budgets before the Sentry drain. + const hookSpy = spyOn(hooks, 'executeSessionEndHooks') + .mockImplementation(() => sleep(1500)) + restoreHooks = () => { hookSpy.mockRestore() } + unregisterCleanup = registerCleanup(() => sleep(2000)) + jest.useFakeTimers() + } + if (mode === 'failsafe' || mode === 'sync-rejection') { + const hooks = await import('../../utils/hooks.js') + if (mode === 'failsafe') { + // A hook that ignores its abort signal leaves only the failsafe able to exit. + const hookSpy = spyOn(hooks, 'executeSessionEndHooks').mockImplementation(() => { + sessionEndHooksStarted = true + return new Promise(() => {}) + }) + restoreHooks = () => { hookSpy.mockRestore() } + } else { + // Fail before the failsafe is armed or the normal-path flush can run. + const timeoutSpy = spyOn(hooks, 'getSessionEndHookTimeoutMs').mockImplementation(() => { + throw new Error('shutdown setup failed') + }) + restoreHooks = () => { timeoutSpy.mockRestore() } + } + jest.useFakeTimers() + } + + process.exit = ((code?: number) => { + exitCode = code + flushedBeforeExit = flushed + notifyExit() + // The failsafe runs in an async timer callback; avoid an unhandled rejection. + if (mode === 'failsafe') return undefined as never + throw exitError + }) as typeof process.exit + + let shutdown: Promise + if (mode === 'sync-rejection') { + gracefulShutdownSync(7) + const pending = getPendingShutdownForTesting() + assert(pending, 'sync shutdown must expose its catch-handler completion') + shutdown = pending + } else { + shutdown = gracefulShutdown(7).catch(error => { + if (error !== exitError) throw error + }) + } + if (mode === 'failsafe') { + // Drive the actual armed timer while the normal shutdown stays in its hook. + for (let elapsed = 0; elapsed < 10_000 && flushCalls === 0; elapsed += 10) { + jest.advanceTimersByTime(10) + for (let i = 0; i < 20; i++) await Promise.resolve() + } + assert(sessionEndHooksStarted, 'normal shutdown must be stuck in its hook') + assert.equal(flushCalls, 1, 'failsafe must begin a Sentry drain') + } + if (mode === 'default-budget') { + // Advance through shutdown until the normal-path flush signals readiness. + // Yield between ticks so each awaited shutdown stage can schedule its timers. + for (let elapsed = 0; elapsed < 5000 && flushCalls === 0; elapsed += 10) { + jest.advanceTimersByTime(10) + for (let i = 0; i < 20; i++) await Promise.resolve() + } + assert.equal(flushCalls, 1, 'normal-path Sentry flush must start') + assert.equal(exitCode, undefined, 'shutdown must wait for flush') + // Cross the old 5s failsafe deadline while the normal flush is still pending. + jest.advanceTimersByTime(1999) + for (let i = 0; i < 20; i++) await Promise.resolve() + assert.equal(flushCalls, 1, 'failsafe must not start another flush during the normal drain') + assert.equal(exitCode, undefined, 'shutdown must not exit before the normal flush finishes') + releaseFlush() + } + if (mode === 'enabled' || mode === 'blocked' || mode === 'failsafe' || mode === 'sync-rejection') { + await Promise.race([ + flushStarted, + shutdown.then(() => { throw new Error('exited without flushing Sentry') }), + ]) + assert.equal(exitCode, undefined, 'shutdown must wait for flush') + jest.advanceTimersByTime(1999) + // Let an incorrectly early deadline propagate through the awaited helpers. + for (let i = 0; i < 5; i++) await Promise.resolve() + assert.equal(exitCode, undefined, 'flush deadline must not expire early') + if (mode === 'blocked') jest.advanceTimersByTime(1) + else releaseFlush() + } + // The failsafe exits despite the deliberately hung hook in normal shutdown. + if (mode === 'failsafe') await exitObserved + else await shutdown + console.log(JSON.stringify({ flushCalls, flushTimeout, exitCode, flushedBeforeExit })) +} finally { + unregisterCleanup?.() + restoreHooks?.() + jest.useRealTimers() + process.exit = originalExit + process.exitCode = 0 + resetShutdownState() + restoreEnv() +} diff --git a/src/utils/gracefulShutdown.sentry.test.ts b/src/utils/gracefulShutdown.sentry.test.ts new file mode 100644 index 000000000..d5dbe1456 --- /dev/null +++ b/src/utils/gracefulShutdown.sentry.test.ts @@ -0,0 +1,41 @@ +import { expect, test } from 'bun:test' +import { fileURLToPath } from 'node:url' + +test.each([ + ['enabled', 1, true], + ['default-budget', 1, true], + ['failsafe', 1, true], + ['sync-rejection', 1, true], + ['dsn-unset', 0, false], + ['telemetry-disabled', 0, false], + ['nonessential-disabled', 0, false], + ['blocked', 1, false], + ['throws', 1, false], + ['rejects', 1, false], +] as const)('graceful shutdown handles Sentry flush: %s', async (mode, flushCalls, flushedBeforeExit) => { + // Isolate SDK state, process.exit, and fake timers from the shared test runner. + // The test deadline leaves time to collect output after the child deadline. + const child = Bun.spawn([ + process.execPath, + fileURLToPath(new URL('../test/fixtures/sentryShutdown.fixture.ts', import.meta.url)), + mode, + ], { + cwd: fileURLToPath(new URL('../../', import.meta.url)), + stdout: 'pipe', + stderr: 'pipe', + timeout: 10_000, + }) + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(child.stdout).text(), + new Response(child.stderr).text(), + child.exited, + ]) + expect(stderr).toBe('') + expect(exitCode).toBe(0) + expect(JSON.parse(stdout)).toEqual({ + flushCalls, + ...(flushCalls ? { flushTimeout: 2000 } : {}), + exitCode: 7, + flushedBeforeExit, + }) +}, 15_000) diff --git a/src/utils/gracefulShutdown.ts b/src/utils/gracefulShutdown.ts index 849ab7b89..f5d8c55e8 100644 --- a/src/utils/gracefulShutdown.ts +++ b/src/utils/gracefulShutdown.ts @@ -30,7 +30,11 @@ import { wrapForMultiplexer, } from '../ink/termio/osc.js' import { shutdownDatadog } from '../services/analytics/datadog.js' -import { reportErrorToSentry } from './sentry.js' +import { + flushSentry, + reportErrorToSentry, + SENTRY_FLUSH_TIMEOUT_MS, +} from './sentry.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, @@ -370,10 +374,11 @@ export function gracefulShutdownSync( process.exitCode = exitCode pendingShutdown = gracefulShutdown(exitCode, reason, options) - .catch(error => { + .catch(async error => { logForDebugging(`Graceful shutdown failed: ${error}`, { level: 'error' }) cleanupTerminalModes() printResumeHint() + await flushSentry() forceExit(exitCode) }) // Prevent unhandled rejection: forceExit re-throws in test mode, @@ -441,14 +446,16 @@ export async function gracefulShutdown( // Failsafe: guarantee process exits even if cleanup hangs (e.g., MCP connections). // Runs cleanupTerminalModes first so a hung cleanup doesn't leave the terminal dirty. - // Budget = max(5s, hook budget + 3.5s headroom for cleanup + analytics flush). + // Budget = max(5s, hook budget + 3.5s cleanup/analytics headroom + Sentry drain). + // If cleanup hangs, the failsafe gives Sentry one bounded drain before exit. failsafeTimer = setTimeout( - code => { + async code => { cleanupTerminalModes(true) printResumeHint() + await flushSentry() forceExit(code) }, - Math.max(5000, sessionEndTimeoutMs + 3500), + Math.max(5000, sessionEndTimeoutMs + 3500 + SENTRY_FLUSH_TIMEOUT_MS), exitCode, ) failsafeTimer.unref() @@ -558,6 +565,7 @@ export async function gracefulShutdown( flushInterruptionTrace('graceful_shutdown') await Promise.race([waitForInterruptionTraceFlush(), sleep(500)]) + await flushSentry() forceExit(exitCode) } diff --git a/src/utils/sentry.test.ts b/src/utils/sentry.test.ts index 3935a8b49..5042ba4b2 100644 --- a/src/utils/sentry.test.ts +++ b/src/utils/sentry.test.ts @@ -1,25 +1,20 @@ -import { afterEach, beforeEach, expect, mock, test } from 'bun:test' +import { afterEach, beforeEach, expect, test } from 'bun:test' +import { hostname } from 'node:os' +import { fileURLToPath } from 'node:url' +import { snapshotEnv } from '../test/envSnapshot.js' -const ORIGINAL_SENTRY_DSN = process.env.SENTRY_DSN -const ORIGINAL_DISABLE_TELEMETRY = process.env.DISABLE_TELEMETRY +let restoreEnv: () => void beforeEach(() => { - delete process.env.SENTRY_DSN - delete process.env.DISABLE_TELEMETRY + restoreEnv = snapshotEnv([ + 'SENTRY_DSN', + 'DISABLE_TELEMETRY', + 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC', + ], { clear: true }) }) afterEach(() => { - if (ORIGINAL_SENTRY_DSN === undefined) { - delete process.env.SENTRY_DSN - } else { - process.env.SENTRY_DSN = ORIGINAL_SENTRY_DSN - } - if (ORIGINAL_DISABLE_TELEMETRY === undefined) { - delete process.env.DISABLE_TELEMETRY - } else { - process.env.DISABLE_TELEMETRY = ORIGINAL_DISABLE_TELEMETRY - } - mock.restore() + restoreEnv() }) test('isSentryEnabled is false when SENTRY_DSN is unset', async () => { @@ -40,6 +35,13 @@ test('isSentryEnabled is false when SENTRY_DSN is set but DISABLE_TELEMETRY is s expect(isSentryEnabled()).toBe(false) }) +test('isSentryEnabled is false when SENTRY_DSN is set but CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC is set', async () => { + process.env.SENTRY_DSN = 'https://example@o0.ingest.sentry.io/0' + process.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1' + const { isSentryEnabled } = await import('./sentry.js') + expect(isSentryEnabled()).toBe(false) +}) + test('reportErrorToSentry does not throw when Sentry is disabled', async () => { const { reportErrorToSentry } = await import('./sentry.js') const { TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS: TelemetrySafeError } = @@ -60,28 +62,59 @@ test('reportErrorToSentry does not throw for a plain (non-sanitized) Error', asy ).not.toThrow() }) -test('reportErrorToSentry only reports TelemetrySafeError, never a raw Error message', async () => { - process.env.SENTRY_DSN = 'https://example@o0.ingest.sentry.io/0' +test('reportErrorToSentry only reports sanitized messages without stacks using the real SDK', async () => { + // A fresh process avoids shared SDK state and module mocks from other tests. + // Leave time for the child deadline and reading its output during cleanup. + const child = Bun.spawn([process.execPath, '--eval', ` + import * as Sentry from '@sentry/node' + import { flushSentry, initializeSentry, reportErrorToSentry } from '${new URL('./sentry.ts', import.meta.url).href}' + import { TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS as TelemetrySafeError } from '${new URL('./errors.ts', import.meta.url).href}' - const captureMessage = mock(() => {}) - mock.module('@sentry/node', () => ({ - init: mock(() => {}), - captureMessage, - })) - - const { initializeSentry, reportErrorToSentry } = await import('./sentry.js') - const { TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS: TelemetrySafeError } = - await import('./errors.js') - - await initializeSentry() - - // A plain Error must never be reported — its message may contain file paths. - reportErrorToSentry(new Error('raw error with /some/file/path')) - expect(captureMessage).not.toHaveBeenCalled() - - // A TelemetrySafeError reports only its sanitized telemetryMessage. - reportErrorToSentry( - new TelemetrySafeError('full message with /some/file/path', 'sanitized message'), - ) - expect(captureMessage).toHaveBeenCalledWith('sanitized message', 'error') -}) \ No newline at end of file + await initializeSentry() + const client = Sentry.getClient() + if (!client) throw new Error('SDK was not initialized') + const transport = client.getTransport() + if (!transport) throw new Error('SDK transport was not initialized') + const events = [] + // Inspect outgoing envelopes without sending any network requests. + transport.send = async envelope => { + for (const [header, event] of envelope[1]) { + if (header.type === 'event') events.push(event) + } + return { statusCode: 200 } + } + reportErrorToSentry(new Error('raw error with /some/file/path')) + reportErrorToSentry(new TelemetrySafeError('full message with /some/file/path', 'sanitized message')) + await flushSentry() + const options = client.getOptions() + console.log(JSON.stringify({ events, integrations: options.integrations.map(i => i.name), tracesSampleRate: options.tracesSampleRate })) + await Sentry.close(1000) + `], { + cwd: fileURLToPath(new URL('../../', import.meta.url)), + env: { ...process.env, SENTRY_DSN: 'https://example@o0.ingest.sentry.io/0' }, + stdout: 'pipe', + stderr: 'pipe', + timeout: 10_000, + }) + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(child.stdout).text(), + new Response(child.stderr).text(), + child.exited, + ]) + expect(stderr).toBe('') + expect(exitCode).toBe(0) + const result = JSON.parse(stdout) + expect(result.integrations).toEqual([]) + expect(result.tracesSampleRate).toBeUndefined() + expect(result.events).toHaveLength(1) + expect(result.events[0].message).toBe('sanitized message') + expect(result.events[0].level).toBe('error') + expect(result.events[0].exception).toBeUndefined() + expect(result.events[0].stacktrace).toBeUndefined() + expect(result.events[0].user).toBeUndefined() + expect(result.events[0].request).toBeUndefined() + expect(result.events[0].server_name).toBeUndefined() + expect(result.events[0].hostname).toBeUndefined() + expect(stdout).not.toContain(hostname()) + expect(stdout).not.toContain('/some/file/path') +}, 15_000) diff --git a/src/utils/sentry.ts b/src/utils/sentry.ts index 4f5c1bd01..019e93ca6 100644 --- a/src/utils/sentry.ts +++ b/src/utils/sentry.ts @@ -9,6 +9,9 @@ */ import { isTelemetryDisabled } from './privacyLevel.js' import { TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS as TelemetrySafeError } from './errors.js' +import { sleep } from './sleep.js' + +export const SENTRY_FLUSH_TIMEOUT_MS = 2000 let sentryInitialized = false let sentryModule: typeof import('@sentry/node') | null = null @@ -36,7 +39,30 @@ export async function initializeSentry(): Promise { sentryModule.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV ?? 'production', - tracesSampleRate: 0, + // Leave tracing sample options unset; even a zero rate enables span recording. + // Keep v11's span streaming and runtime instrumentation out of this path. + traceLifecycle: 'static', + enableOpenTelemetrySetup: false, + enableRuntimeChannelInjection: false, + // v11 attaches synthetic stacks to messages and collects more data by + // default. Keep this reporter limited to the vetted message. + attachStacktrace: false, + // The SDK otherwise adds os.hostname() as server_name to every event. + includeServerName: false, + dataCollection: { + userInfo: false, + cookies: false, + httpHeaders: false, + httpBodies: [], + urlQueryParams: false, + graphQL: { document: false, variables: false }, + genAI: { inputs: false, outputs: false }, + mcp: { inputs: false, outputs: false }, + databaseQueryData: false, + queues: false, + stackFrameVariables: false, + frameContextLines: 0, + }, // Disable Sentry's automatic uncaughtException/unhandledRejection // integrations. Those hooks report raw error content, bypassing the // TelemetrySafeError sanitization in reportErrorToSentry(). Only @@ -69,4 +95,24 @@ export function reportErrorToSentry(error: unknown): void { } catch { // Reporting must never throw. } -} \ No newline at end of file +} + +/** Drain opted-in reports before exit, without allowing the SDK to delay shutdown indefinitely. */ +export async function flushSentry(): Promise { + if (!sentryModule || !isSentryEnabled()) { + return + } + + const deadline = new AbortController() + try { + // The SDK timeout does not bound every stage of its flush pipeline. + await Promise.race([ + sentryModule.flush(SENTRY_FLUSH_TIMEOUT_MS), + sleep(SENTRY_FLUSH_TIMEOUT_MS, deadline.signal), + ]) + } catch { + // Reporting failures must never prevent process exit. + } finally { + deadline.abort() + } +}