diff --git a/.github/prompts/code-review.prompt.md b/.github/prompts/code-review.prompt.md index 2c74a9862345..5ce7b65798f1 100644 --- a/.github/prompts/code-review.prompt.md +++ b/.github/prompts/code-review.prompt.md @@ -1,8 +1,12 @@ # Copilot code review prompt +# +# This is a VS Code prompt file. Invoke it on-demand in Copilot Chat +# with /code-review. It is NOT automatically loaded — users choose to +# use it. For always-on instructions, see .github/copilot-instructions.md. -Review these code changes adversarially. Assume every change is guilty until proven correct. Your job is to find bugs, security holes, performance traps, and subtle logic errors that the author missed. +Review these code changes carefully. Your job is to find bugs, security holes, performance traps, and logic errors that the author missed. -DO NOT MODIFY ANY FILES except the review report. Only provide feedback and suggestions. +DO NOT MODIFY ANY FILES. Only provide feedback and suggestions. ## Finding changes to review @@ -12,29 +16,22 @@ Use git to find the files changed on this branch compared to main: git diff --name-only $(git merge-base main HEAD)...HEAD ``` -Only review files returned by this command. Do not review files that are not in this list. - -IMPORTANT: Only review committed changes. - -DO NOT review: -- Staged changes (files in the index) -- Unstaged changes (modified files not yet staged) -- Untracked files +Only review committed changes returned by this command. Do not review staged, unstaged, or untracked files. ## Coding standards -**CRITICAL**: Before reviewing any code, use `read_file` to read each of these instruction files in full: +**CRITICAL**: Before reviewing any code, read each of these instruction files in full: - .github/instructions/all.instructions.md (general project guidelines) - .github/instructions/code.instructions.md (for TypeScript/JavaScript and code files) - .github/instructions/content.instructions.md (for .md content files) - .github/instructions/style-guide-summary.instructions.md (for documentation style) -Read all instruction files FULLY without limit/offset parameters. Do NOT skip this step or rely on summaries. Apply the standards from these files when reviewing changed code. +Apply the standards from these files when reviewing changed code. ## Paths to ignore -IGNORE these paths entirely - do not review or comment on them: +IGNORE these paths entirely: - node_modules/* - .next/* @@ -46,271 +43,103 @@ IGNORE these paths entirely - do not review or comment on them: - Violations of the project's coding standards (especially: all new code must be TypeScript, use `@/` absolute imports, scripts in `package.json` that execute TypeScript source files must use `tsx`) - Bugs and logic errors - Error handling issues -- Security concerns -- Defensive programming: Functions should validate inputs at trust boundaries. Specifically check for: - - `undefined`/`null` access that will throw at runtime (e.g., destructuring a possibly-undefined object, indexing into a possibly-null array) - - Optional chaining (`?.`) used where a hard failure would be more appropriate (silently returning `undefined` instead of surfacing a bug) - - Type assertions (`as`, `!`) that bypass the type system—each one must be justified or flagged -- Configuration consistency: Ports, URLs, or service names hardcoded in `docker-compose.yaml`, `Dockerfile`, or `config/` must match what `package.json` scripts and env vars reference. +- Security concerns (see Security section below) +- Defensive programming: check for `undefined`/`null` access that will throw at runtime, type assertions (`as`, `!`) that bypass the type system without justification +- Configuration consistency: ports, URLs, or service names in `docker-compose.yaml`, `Dockerfile`, or `config/` must match what `package.json` scripts and env vars reference - Documentation accuracy: JSDoc comments, README text, test descriptions, and inline comments must match actual behavior -## TypeScript-specific code quality - -For `.ts` and `.tsx` files, check for: - -- **`any` leaks**: Any use of `any` that could be replaced with a proper type, `unknown`, or a generic. Each `any` is a hole in the type system. `as any` to silence a compiler error is almost always a bug in disguise. -- **Unchecked `as` casts**: Type assertions that narrow without a runtime guard. Example: `const user = data as User` without validating `data` actually matches `User`. Prefer type guards or schema validation (e.g., zod). -- **Promise handling**: Every `async` function call or Promise must be `await`ed, returned, or explicitly voided with `void promise`. Unhandled promises silently swallow errors and are a top source of production mystery failures. -- **Floating promises in Express/Next handlers**: An `async` function passed to `app.get()` or used as a Next.js API route that throws will NOT be caught by Express/Next—it becomes an unhandled rejection. Verify error boundaries exist. -- **`===` vs `==`**: All comparisons must use strict equality. Flag any `==` or `!=` usage. -- **Mutable default parameters**: Function parameters with mutable default values (e.g., `function foo(opts = {})`) can share state across calls if the default is mutated. Ensure defaults are not mutated. -- **Import hygiene**: All imports must use the `@/` absolute path convention per project rules. No relative imports (`../`) reaching outside the current directory's subtree. `import type` must be used for type-only imports. -- **Dead exports**: Exported functions, types, or constants that are not imported anywhere else in the codebase. Run `grep -r` to verify usage. -- **Implicit return types on exported functions**: Exported functions should have explicit return type annotations. Inferred returns leak implementation details and can silently break callers when the implementation changes. - -## Performance and reliability (HOT PATH) - -These are the highest-priority review checks. Every issue found here is 🔴 Critical or 🟠 High by default. - -### Blocking the event loop - -Node.js is single-threaded. Any synchronous work in a request handler, middleware, or server-side Next.js function blocks ALL concurrent requests. Flag any of these on a code path reachable from an HTTP request: - -- **Synchronous file system calls**: `fs.readFileSync`, `fs.writeFileSync`, `fs.existsSync`, `fs.statSync`, `fs.readdirSync`, `fs.accessSync`, or any `*Sync` method from `fs` or `node:fs`. These must use their async counterparts (`fs.promises.*` or callback-based) or be moved to build-time/startup-only code. -- **Synchronous child processes**: `execSync`, `spawnSync`, `execFileSync` in request-handling code. -- **CPU-intensive computation**: `JSON.parse()` on unbounded/user-controlled input, large regex matches, synchronous crypto (`crypto.pbkdf2Sync`, `crypto.scryptSync`, `crypto.randomBytes` without callback), or tight loops iterating over large datasets. These should be streaming, chunked, or offloaded to a worker thread. -- **Synchronous `require()`**: Dynamic `require()` calls at request time (not at module load). `require()` is synchronous and hits the filesystem on first call. -- **Blocking iteration patterns**: `Array.prototype.sort()`, `.filter().map().reduce()` chains on arrays that could be large (e.g., all pages in the docs site). Ask: what is the upper bound of this array's length? - -### File IO on the hot path - -Any file read/write reachable from an HTTP request handler is suspect. Check for: +## Content and Liquid checks -- **Unbounded file reads**: `fs.readFile` (async or sync) on files whose size is not bounded. Reading a multi-megabyte file into memory per request is a DoS vector. Prefer streaming with `fs.createReadStream` and pipe to the response. -- **Repeated reads of the same file**: Files read on every request that could be read once at startup and cached. Example: reading a YAML config file, parsing markdown, or loading a JSON data file inside a request handler instead of at module initialization. -- **No error handling on file operations**: `fs.readFile` or `fs.createReadStream` without handling `ENOENT`, `EACCES`, or `EMFILE` (too many open files). Missing error handling on streams is especially dangerous—an uncaught `'error'` event crashes the process. -- **`fs.watch`/`fs.watchFile` in production**: File watchers are appropriate in development but leak file descriptors in production. Verify they are gated behind `NODE_ENV === 'development'` or similar. -- **Temporary file cleanup**: Code that writes temp files (`/tmp`, `os.tmpdir()`) must clean them up in a `finally` block. Missing cleanup under error paths causes disk exhaustion over time. +For changes to `.md` files in `content/` or `data/`: -### Caching correctness - -In-memory caches are the #1 source of subtle production bugs. For any caching (in-memory `Map`/`Object`, LRU, `node-cache`, module-level variables, Next.js `unstable_cache`, HTTP cache headers), verify: - -- **Unbounded growth**: Any `Map`, `Set`, `Object`, or array used as a cache that grows without eviction. Module-level `const cache = new Map()` that is populated on each unique request key but never pruned will eventually OOM the process. Require an eviction strategy (LRU, TTL, max size) or justify why the keyspace is bounded. -- **Missing cache invalidation**: Cached data derived from mutable sources (database, file system, external API) with no TTL or invalidation mechanism. Ask: when the source data changes, how does the cache learn about it? -- **Cache key collisions**: Cache keys that do not fully encode the request context. Example: caching by `pathname` alone when the response also varies by `version`, `language`, or user role. Two different users hitting the same cache key get each other's data. -- **Caching user-specific data globally**: Any cache that stores per-user or per-session data in a module-level variable. This leaks data between requests/users in the same process. In Next.js, also check for accidentally caching inside a shared server component module. -- **Stale reads after writes**: Code that writes to a data source then immediately reads from a cache that still holds the old value. Verify write-through or cache-bust-on-write semantics. -- **Cache stampede**: When a popular cache entry expires, many concurrent requests may simultaneously compute the same expensive value. Check if there is a lock/dedup mechanism for expensive recomputation. -- **Next.js-specific caching pitfalls**: - - `fetch()` in server components is cached by default. Verify `{ cache: 'no-store' }` or `{ next: { revalidate: N } }` is set appropriately for data that changes. - - `unstable_cache` keys must be unique and include all parameters that affect the result. - - Static generation caching persists across deploys unless `revalidate` is configured. - -### Memory leaks - -A memory leak in a long-running Node.js server is a ticking time bomb. For any new code, check: - -- **Event listener accumulation**: `emitter.on()` or `addEventListener()` called in a request handler or repeated code path without a corresponding `removeListener()` / `removeEventListener()`. Each request adds a listener that is never removed. Node warns at 11 listeners but the damage is done earlier. Also check for `.once()` where `.on()` was intended (and vice versa). -- **Uncleared timers**: `setInterval()` or `setTimeout()` created in request-scoped code without `clearInterval()`/`clearTimeout()` in cleanup/error paths. `setInterval` is especially dangerous—if the handle is lost, the interval runs forever. -- **Closure retention**: Functions or callbacks that close over large objects (request/response bodies, parsed HTML trees, database result sets) and are then stored in a long-lived structure (module-level cache, event emitter, global array). The large object cannot be GC'd as long as the closure is reachable. -- **Stream pipe leaks**: `stream.pipe(dest)` without error handling. If the source errors, the destination is not automatically cleaned up (and vice versa). Use `pipeline()` from `node:stream/promises` which handles cleanup. Also check for readable streams created but never consumed (they buffer in memory). -- **Global/module-level arrays and maps used as queues**: Arrays that `.push()` items but rely on a consumer to `.shift()` them. If the consumer fails or falls behind, the array grows without bound. -- **WeakRef misuse**: `WeakRef` / `FinalizationRegistry` used for correctness (not just optimization). GC timing is non-deterministic—code must not depend on weak references being collected at any particular time. -- **Express `req`/`res` captured in closures that outlive the request**: Middleware that stores `req` or `res` in a module-level structure (for logging, metrics, or tracing) must use weak references or clear the reference when the response finishes (`res.on('finish', ...)`). - -## Security checks (Node/Next.js-specific) - -For API routes, middleware, and server-side code, verify: - -- **Error exposure**: Errors returned to clients should NOT include raw `error.message` or `error.stack` which may leak internal details (file paths, dependency versions, SQL queries). Use generic messages and log the real error server-side. -- **Fail-safe defaults**: Boolean conditions controlling access should fail closed (deny by default), not fail open. Example: `const isDev = env === 'development'` (allowlist) NOT `const isDev = env !== 'production'` (denylist—allows `staging`, `test`, typos, and `undefined`). -- **Input validation**: All user inputs from `req.query`, `req.body`, `req.params`, URL search params, and headers must be validated and typed before use. Never trust that a query param is a string—it could be an array. -- **Prototype pollution**: Object spread (`{ ...userInput }`) or `Object.assign({}, userInput)` on unsanitized user input can inject `__proto__`, `constructor`, or `prototype` keys. Flag any code that merges user-controlled objects into configuration or state. -- **Path traversal**: Any use of user-supplied strings in `fs.readFile`, `path.join`, `path.resolve`, or dynamic `import()` must be validated against traversal (`../`, encoded variants). `path.join` does NOT sanitize—`path.join('/safe', '../../etc/passwd')` resolves outside the root. -- **ReDoS**: Regular expressions applied to user input must not contain catastrophic backtracking patterns (nested quantifiers like `(a+)+`, `(a|a)*`). Flag any regex operating on untrusted strings. -- **SSRF in fetch/axios**: Server-side `fetch()` or `axios` calls using user-supplied URLs or URL components must validate the target is not an internal/private IP range (`127.0.0.1`, `169.254.169.254`, `10.*`, `172.16-31.*`, `192.168.*`, `[::1]`, `localhost`). -- **Environment variable trust**: Code should not branch on `process.env.NODE_ENV` for security decisions. `NODE_ENV` is a build/runtime hint, not a security boundary. - -## Test quality checks - -For test files (`*.test.ts`, `*.spec.ts`, files under `tests/`): +* **Liquid variables**: Always use `{% data variables.product.* %}` for product names — never hardcode. Check `data/variables/` for existing variables before introducing hardcoded text. +* **Internal links**: Must use `[AUTOTITLE](/path/to/article)` — never hardcode article titles in link text. +* **Bullet lists**: Must use `*` (asterisks), never `-` (hyphens). +* **Frontmatter**: Verify `versions`, `type`, `layout`, and other frontmatter fields conform to the schema in `src/frame/lib/frontmatter.ts`. +* **Versioning**: `ifversion` tags must use valid version names. Check for unclosed Liquid tags. +* **Reusables**: If the same text appears in multiple articles, it should be a reusable in `data/reusables/`. -- **Test isolation**: Tests must not depend on execution order or shared mutable state. Each test should set up and tear down its own state. -- **Assertion quality**: Tests should assert on observable outputs (return values, HTTP status codes, response bodies, DOM state) not implementation details. `expect(spy).toHaveBeenCalled()` without checking *what* it was called with is a weak assertion. -- **Missing negative tests**: For every happy path, ask: where is the test for invalid input, network failure, timeout, or permission denied? -- **Snapshot overuse**: Snapshots that capture large objects or HTML trees are brittle and encourage "update snapshot" without review. Flag new snapshots over 20 lines. -- **Hardcoded ports/URLs in tests**: Tests that hardcode `localhost:4000` or similar will fail in parallel or in CI environments with different port assignments. -- **Async test correctness**: Verify `async` tests actually `await` the operations they are testing. A test that creates a promise but does not await it will always pass (it resolves after the test completes). +## Frontmatter schema changes -## Test coverage check +When `src/frame/lib/frontmatter.ts` is modified: -For new code in implementation files: +- Verify the schema change is reflected in `src/types/types.ts` +- Check if content linter rules in `src/content-linter/` need updating +- Verify fixture content in `src/fixtures/fixtures/` is updated to match +- Check if `content/contributing/writing-for-github-docs/using-yaml-frontmatter.md` needs updating -1. Identify new exported functions, types, and config fields -2. Check that corresponding test files exist and cover the new code -3. Flag any new config fields (especially `required` ones) that lack test coverage -4. For each modified module with new or changed code, check existing tests cover the new paths. Flag any new exported function with no corresponding test as a critical gap. +## Content linter rule changes -## External call performance +When adding or modifying rules in `src/content-linter/`: -For any code making outbound HTTP requests (`fetch`, `axios`, `node-fetch`, or any HTTP client): +- Read `src/content-linter/README.md` for conventions +- Verify the rule is registered in `src/content-linter/lib/linting-rules/index.ts` +- Verify the rule is configured in `src/content-linter/style/github-docs.ts` +- Check that `data/reusables/contributing/content-linter-rules.md` is updated with the rule description -- **Missing timeouts**: Every outbound `fetch()` call on a request-handling code path MUST use `fetchWithTimeout` (from `@/frame/lib/fetch-utils`) or provide an `AbortSignal` with a timeout. A `fetch()` without a timeout will hang the request (and the event loop slot) indefinitely if the remote server is slow or unresponsive. Note: `fetchWithTimeout` clears its timer when headers arrive—it does NOT cover slow body streaming. If the response body is large or streamed, additional timeout handling is needed. -- **Missing retries with backoff**: Calls to external services that can transiently fail should use `fetchWithRetry` (from `@/frame/lib/fetch-utils`). Bare `fetch()` with a try/catch that gives up on the first failure will cause unnecessary user-facing errors during brief upstream blips. -- **Unbounded retry loops**: If retries ARE used, verify there is a maximum retry count and that backoff is exponential (not fixed-delay). `fetchWithRetry` already handles this, but custom retry logic might not. -- **N+1 fetch patterns**: Sequential `await fetch()` calls inside a `for`/`for...of`/`.forEach`/`.map` loop. If the iterations are independent, they should be parallelized with `Promise.all()` (with a concurrency limit if the count is unbounded). Example anti-pattern: - ```typescript - // 🔴 BAD: 50 sequential round-trips - for (const id of ids) { - const data = await fetch(`/api/${id}`) - } - // ✅ GOOD: parallel with concurrency limit - await Promise.all(ids.map(id => fetch(`/api/${id}`))) - ``` -- **Missing error classification**: Fetch errors should distinguish between network failures (retry-safe), 4xx client errors (do not retry, likely a bug), and 5xx server errors (retry-safe). Code that retries on 400 or 404 is wasting time and masking bugs. -- **Response body consumption**: Every `fetch()` response body MUST be consumed (`.json()`, `.text()`, `.arrayBuffer()`, or explicitly `.body?.cancel()`). Unconsumed response bodies leak sockets and can exhaust the connection pool. This is especially easy to miss in error paths where the response is discarded. +## TypeScript-specific quality -## Middleware and Express checks +For `.ts` and `.tsx` files: -For Express middleware and request handlers: +* **`any` leaks**: Flag `any` that could be replaced with a proper type or `unknown`. `as any` to silence a compiler error is almost always a bug. +* **Promise handling**: Every `async` function call must be `await`ed, returned, or explicitly voided. Unhandled promises silently swallow errors. +* **Import hygiene**: All imports must use `@/` absolute paths. `import type` must be used for type-only imports. +* **Implicit return types on exported functions**: Exported functions should have explicit return type annotations. -- **Middleware ordering**: New middleware must be inserted at the correct position in the chain. Auth/security middleware must run before route handlers. Logging middleware should capture both request and response. -- **Missing `next()` calls**: Every middleware must call `next()` or send a response. A middleware that conditionally calls `next()` but has a code path that does neither will hang the request. -- **Response after `res.end()`**: Verify no code path calls `res.json()`, `res.send()`, or `res.redirect()` after the response has already been sent. This causes "headers already sent" errors. -- **Memory leaks in closures**: Middleware closures that capture large objects (request bodies, database connections) and store them in module-level caches or event listeners that are never cleaned up. -- **Streaming response handling**: If using `res.write()` for streaming, verify error handling on the writable stream and that the response is properly ended in all code paths (including errors). +## Performance on request paths -## Observability and metric hygiene +This is a Node.js/Next.js (Pages Router) application serving docs.github.com. Any synchronous work in a request handler blocks concurrent requests. -This project uses `hot-shots` StatsD (prefix `docs.`, global tag `app:docs`) configured in `src/observability/lib/statsd.ts`, and a structured logger in `src/observability/logger/index.ts`. Every review must enforce these standards: +* **Synchronous file system calls**: `readFileSync`, `existsSync`, `writeFileSync`, etc. in request-handling code (middleware, API routes). These should use async counterparts or be cached at startup. Note: the codebase has some existing sync calls in middleware (e.g., `favicons.ts`, `find-page.ts`) — flag only NEW sync calls on hot paths. +* **Unbounded iteration**: `.sort()`, `.filter().map()` chains on arrays that could be large (e.g., all pages). Ask: what is the upper bound? +* **Unbounded caches**: Module-level `Map` or `Object` used as a cache that grows without eviction. Require an eviction strategy or justify why the keyspace is bounded. See `src/frame/lib/get-remote-json.ts` for the existing caching pattern. +* **Missing timeouts on outbound fetch**: Server-side `fetch()` calls on request paths should use `fetchWithRetry` from `@/frame/lib/fetch-utils` or provide an `AbortSignal` with a timeout. Bare `fetch()` without a timeout will hang indefinitely if the remote is unresponsive. -### Metric cardinality (🔴 Critical when violated) +## Security checks -High-cardinality tags are the #1 cause of metric cost explosions and Datadog aggregation failures. For ANY new `statsd.increment()`, `statsd.histogram()`, `statsd.gauge()`, or `statsd.asyncTimer()` call, verify: +For API routes, middleware, and server-side code: -- **No user-controlled values in tags**: Tags like `path:${req.path}`, `url:${req.url}`, `query:${req.query.q}`, or `user:${userId}` create a unique time series per unique value. With thousands of docs pages and infinite query strings, this quickly creates millions of time series. Tags must use bounded, enumerable values (e.g., `product:actions`, `status:200`, `cache:hit`). -- **Audit existing high-cardinality patterns being copied**: The codebase already has some `path:${req.pagePath}` tags (in `abort.ts`, `handle-errors.ts`, `render-page.ts`). New code MUST NOT copy this pattern without justification. If the tag is on a low-volume error/abort path, it may be acceptable. If it is on a hot path (every request), it is not. -- **Tag value normalization**: If a path-like tag is genuinely needed, it must be normalized to a bounded set. Example: use the top-level product category (`actions`, `copilot`, `rest`) not the full URL path. Use route patterns (`/[version]/[product]/[...slug]`) not resolved paths. -- **New metric names**: Every new metric name should follow the existing `docs..` convention (e.g., `docs.middleware.render_page`, `docs.cache.lookup`). Inconsistent naming fragments the metric namespace. -- **Missing metrics on new code paths**: New middleware, new API routes, and new cache layers should emit at minimum: - - A counter for requests/invocations - - A timer/histogram for latency (use `statsd.asyncTimer()` for async functions) - - For caches: hit/miss counter and size gauge (see `src/frame/lib/get-remote-json.ts` for the pattern) -- **Tag consistency**: Tags on related metrics must use the same key names and value sets. Example: if one metric uses `cache:remote_json` and another uses `cache_name:remote_json`, dashboards and alerts will miss the correlation. +* **Error exposure**: Errors returned to clients should NOT include `error.message` or `error.stack`. Use generic messages and log the real error server-side. +* **Input validation**: All user inputs from `req.query`, `req.body`, `req.params` must be validated. A query param could be an array, not a string. +* **Path traversal**: User-supplied strings used in `fs.readFile`, `path.join`, or dynamic `import()` must be validated. `path.join('/safe', '../../etc/passwd')` resolves outside the root. +* **Fail-safe defaults**: Boolean conditions controlling access should fail closed. Use `env === 'development'` (allowlist) not `env !== 'production'` (denylist). -### Structured logging +## Observability -- **No `console.log`/`console.warn`/`console.error` in new code**: The project has a structured logger (`import { createLogger } from '@/observability/logger'`). All new logging must use it. `console.*` calls bypass log levels, structured context (request UUID, path, version), and production logfmt formatting. Flag any new `console.*` call in `src/` that is not inside a test file. -- **Logger instantiation**: Each module should create its own logger with `const logger = createLogger(import.meta.url)` so logs include the source file. Flag loggers created without `import.meta.url`. -- **Log level correctness**: `logger.error()` is for unexpected failures that need human attention. `logger.warn()` is for degraded but recoverable situations. `logger.info()` is for significant state changes. `logger.debug()` is for developer troubleshooting. Flag misuse—especially `logger.info()` on every request (that is debug-level noise) or `logger.error()` for expected conditions (e.g., 404s). -- **Sensitive data in logs**: Log messages must not include auth tokens, cookies, full request bodies, or user-identifiable information. Check for `logger.error('Failed', { headers: req.headers })` or similar patterns that dump entire objects. +This project uses `hot-shots` StatsD (prefix `docs.`, global tag `app:docs`) configured in `src/observability/lib/statsd.ts`, and a structured logger via `createLogger` from `src/observability/logger/index.ts`. -## Next.js-specific checks +* **No `console.log` in new code**: Use `createLogger(import.meta.url)` instead. `console.*` calls bypass log levels and structured context. +* **Metric cardinality**: Tags on `statsd.increment()` / `statsd.histogram()` calls must use bounded, enumerable values. Never use `path:${req.path}` or user-controlled values — they create unbounded time series. +* **New metric names**: Follow the `docs..` convention (e.g., `docs.middleware.render_page`). -For Next.js application code, check for: +## Test quality -- **Client/server boundary violations**: Code importing server-only modules (e.g., `fs`, `path`, database clients, `process.env` secrets) in files that end up in the client bundle. Verify `'use client'` / `'use server'` directives are correct. -- **Data fetching in wrong context**: `getServerSideProps` or server components doing work that should be client-side (or vice versa). Watch for `useEffect` fetches that duplicate server-side data. -- **Middleware correctness**: Next.js middleware runs on every matched request. Verify `matcher` config is not overly broad. Expensive operations in middleware multiply across every request. -- **Dynamic route parameter injection**: `params` from dynamic routes (`[slug]`, `[...catchAll]`) are user-controlled strings. They must be validated/sanitized before use in database queries, file paths, or rendered HTML. -- **Missing error boundaries**: New pages or complex component trees should have error boundaries. An unhandled throw in a React Server Component crashes the entire page. -- **Accidental client bundle bloat**: Importing large server-side libraries in shared modules that are also imported by client components. Check import chains. -- **`headers()`, `cookies()`, `searchParams` in cached contexts**: These dynamic APIs opt a route out of static generation. Verify the author intended dynamic rendering. - -## Dependency security - -When `package.json` or `package-lock.json` is changed, check for: - -- **New dependencies with known vulnerabilities**: Run `npm audit --json` (or check the npm advisory database) for any newly added package. Flag any dependency introduced with known critical or high severity CVEs. -- **Unnecessary new dependencies**: Before accepting a new `dependencies` entry, ask: does the project already have a library that does this? Adding a package for something achievable with existing deps or a few lines of code increases supply chain risk and bundle size. -- **`dependencies` vs `devDependencies`**: Packages used only in tests, linting, or build tooling must be in `devDependencies`, not `dependencies`. Production `dependencies` are installed in the Docker image and loaded at runtime—every unnecessary entry increases attack surface and image size. -- **Pinning and lockfile hygiene**: New entries should have a semver range consistent with the rest of `package.json` (typically `^`). Verify `package-lock.json` is updated and committed alongside `package.json` changes. A `package.json` change without a corresponding lockfile update is a red flag. -- **Postinstall scripts**: Check if a newly added package has `postinstall`, `preinstall`, or `install` scripts by inspecting its `package.json` in `node_modules/`. These scripts run arbitrary code at install time and are a common supply-chain attack vector. -- **Deprecated packages**: Flag any newly added dependency that npm marks as deprecated. Deprecated packages receive no security updates. - -## Bash script checks - -For shell scripts, check for: - -- Working directory assumptions (e.g., `cat .nvmrc` without resolving script dir first) -- Fragile version parsing (e.g., grepping `package.json` instead of using `node -p "require('./package.json').version"`) -- Hard-coded env var requirements that may not be set in all environments (local, CI, production) -- Sourcing files (`. script/foo` or `source script/foo`) without checking the file exists first -- Suppressing stderr with `2>/dev/null` on commands whose failure matters -- `set -e` gotchas: command substitution inside `echo`, `local`, or `readonly` masks the substitution's exit code - -## Docker/container checks - -For `Dockerfile` and `docker-compose.yaml`: - -- Multi-stage build layers that copy `node_modules` from a builder stage must ensure the builder used the same platform/architecture as the runtime stage -- `COPY package*.json ./` before `npm ci` to preserve layer caching. Copying the entire source tree first invalidates the dependency cache on every code change -- Running as `root` in the final stage without dropping privileges -- Missing `NODE_ENV=production` in the runtime stage (affects dependency installation, Next.js optimizations, and Express behavior) -- Health check endpoints that always return 200 without actually verifying service readiness -- Secrets or tokens passed as `ARG`/`ENV` that persist in image layers - -## Verification requirements - -CRITICAL: Before reporting ANY issue, you MUST verify it exists in the actual file. - -Git diff output can be misleading due to: -- Long lines wrapping in terminal display -- Diff excerpts not showing the complete file -- Display artifacts from terminal width - -For each potential issue identified in a diff: - -1. Use `read_file` or `grep` to verify the issue exists in the actual file -2. Only report issues that are confirmed in the source file -3. If verification shows the issue doesn't exist, discard it silently - -Example verification commands: -```bash -# Verify a pattern exists -rg -n "suspected_issue" path/to/file - -# Verify something is missing at end of file -tail -10 path/to/file +For test files (`*.test.ts`, `*.spec.ts`, files under `tests/`): -# Check if a function is exported but never imported -rg -l "import.*functionName" src/ +* **Test isolation**: Tests must not depend on execution order or shared mutable state. +* **Assertion quality**: Assert on observable outputs, not implementation details. +* **Async test correctness**: Verify `async` tests actually `await` the operations they are testing. +* **Missing negative tests**: For every happy path, ask: where is the test for invalid input or failure? -# Verify an `any` type exists -rg -n ": any\b" path/to/file +## Verification -# Check for unhandled promises -rg -n "(?:^|\s)(?!await\s)(?!return\s)(?!void\s)\w+\(" path/to/file.ts -``` - -Do NOT report issues based solely on how the diff appears in terminal output. +Before reporting ANY issue, verify it exists in the actual file using `rg` (ripgrep) or by reading the file. Git diff output can be misleading due to wrapping and context. Do NOT report issues based solely on diff appearance. ## Output format -1. First, write all issues to a markdown file at `tmp/copilot-review-{branch-name}.md` where `{branch-name}` is the current git branch name (replace `/` with `-` in the branch name) - - Example: branch `jmoody/my-feature` → `tmp/copilot-review-jmoody-my-feature.md` -2. The report file should contain: - - A summary section with total issue count by severity (🔴 Critical, 🟠 High, 🟡 Medium, 🔵 Low) - - All issues with file paths and line numbers -3. Then present each issue ONE AT A TIME in the chat, starting with the highest severity -4. For each issue, wait for user feedback before proceeding to the next -5. Format each issue as: +For each issue found: ```markdown -## Issue N: [Brief title] [🔴|🟠|🟡|🔵] +## [🔴|🟠|🟡|🔵] [Brief title] **File**: path/to/file:line **Description**: What the issue is -**Impact**: What happens if this is not fixed (be specific—"could crash" is not enough, say *when* and *how*) -**Suggestion**: How to fix it (include a code snippet if the fix is non-obvious) +**Impact**: What happens if this is not fixed +**Suggestion**: How to fix it (include a code snippet if non-obvious) ``` -After presenting each issue, ask: - -> How would you like to proceed? -> 1. Fix it for me -> 2. I'll fix it differently -> 3. More details please -> 4. Show me other options -> 5. Skip to next issue +Severity guide: +- 🔴 Critical: Will cause runtime errors, security vulnerabilities, or data loss +- 🟠 High: Likely to cause bugs or performance issues under normal usage +- 🟡 Medium: Code quality issue that could lead to future bugs +- 🔵 Low: Style or maintainability concern diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 644c8c38c014..213b537a064d 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -1,3 +1,18 @@ +# Copilot cloud agent setup steps +# +# This is a special-name workflow recognized by Copilot cloud agent. +# When a cloud agent session starts (via GitHub issue assignment or the +# Copilot UI), these steps run first to bootstrap the development +# environment before the agent begins working. +# +# The workflow_dispatch trigger allows manual testing of the setup steps. +# This is NOT a regular CI workflow — it does not run on push or PR events. +# +# See also: +# .github/copilot-instructions.md — always-on agent instructions +# .github/instructions/ — contextual instruction files +# .github/prompts/ — on-demand prompt files (e.g. /code-review) + name: 'Copilot Setup Steps' on: @@ -18,17 +33,21 @@ jobs: - uses: ./.github/actions/node-npm-setup + # Search and language test suites require a running Elasticsearch instance. - uses: ./.github/actions/setup-elasticsearch with: token: ${{ secrets.DOCS_BOT_PAT_BASE }} + # docs-internal has early-access content that must be fetched separately. - uses: ./.github/actions/get-docs-early-access if: ${{ github.repository == 'github/docs-internal' }} with: token: ${{ secrets.DOCS_BOT_PAT_BASE }} + # Many test suites depend on Next.js build artifacts. - name: Build run: npm run build + # Populate Elasticsearch with fixture data so search/language tests work. - name: Index fixtures into the local Elasticsearch run: npm run index-test-fixtures diff --git a/content/actions/reference/workflows-and-actions/contexts.md b/content/actions/reference/workflows-and-actions/contexts.md index 2d07b8e274f1..be69fddf55c0 100644 --- a/content/actions/reference/workflows-and-actions/contexts.md +++ b/content/actions/reference/workflows-and-actions/contexts.md @@ -174,7 +174,7 @@ The `github` context contains information about the workflow run and the event t |---------------|------|-------------| | `github` | `object` | The top-level context available during any job or step in a workflow. This object contains all the properties listed below. | | `github.action` | `string` | The name of the action currently running, or the [`id`](/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsid) of a step. {% data variables.product.prodname_dotcom %} removes special characters, and uses the name `__run` when the current step runs a script without an `id`. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name `__run`, and the second script will be named `__run_2`. Similarly, the second invocation of `actions/checkout` will be `actionscheckout2`. | -| `github.action_path` | `string` | The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action, for example by changing directories to the path: {% raw %} `cd ${{ github.action_path }}` {% endraw %}. | +| `github.action_path` | `string` | The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action, for example by changing directories to the path (using the corresponding enviroment variable): {% raw %} `cd "$GITHUB_ACTION_PATH"` {% endraw %}. For more information on evironment variables, see [AUTOTITLE](/actions/reference/security/secure-use#use-an-intermediate-environment-variable). | | `github.action_ref` | `string` | For a step executing an action, this is the ref of the action being executed. For example, `v2`.

{% data reusables.actions.composite-actions-unsupported-refs %} | | `github.action_repository` | `string` | For a step executing an action, this is the owner and repository name of the action. For example, `actions/checkout`.

{% data reusables.actions.composite-actions-unsupported-refs %} | | `github.action_status` | `string` | For a composite action, the current result of the composite action. | diff --git a/content/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/about-azure-private-networking-for-github-hosted-runners-in-your-enterprise.md b/content/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/about-azure-private-networking-for-github-hosted-runners-in-your-enterprise.md index cbde5cc3f1a6..de88ee2234ae 100644 --- a/content/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/about-azure-private-networking-for-github-hosted-runners-in-your-enterprise.md +++ b/content/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/about-azure-private-networking-for-github-hosted-runners-in-your-enterprise.md @@ -40,6 +40,12 @@ category: {% data reusables.actions.azure-vnet-networking-policies %} +## About VNET failover + +{% data reusables.actions.azure-vnet-about-failover %} + +For more information about configuring a failover network, see [AUTOTITLE](/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/configuring-private-networking-for-github-hosted-runners-in-your-enterprise#5-optionally-add-a-failover-network-to-a-network-configuration). + ## Managing network configuration policies for organizations in your enterprise You can give organization owners in your enterprise the ability to set up and maintain organization-level network configurations for {% data variables.product.company_short %}-hosted runners. diff --git a/content/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/configuring-private-networking-for-github-hosted-runners-in-your-enterprise.md b/content/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/configuring-private-networking-for-github-hosted-runners-in-your-enterprise.md index 497a4522182e..3c001d0ee813 100644 --- a/content/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/configuring-private-networking-for-github-hosted-runners-in-your-enterprise.md +++ b/content/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/configuring-private-networking-for-github-hosted-runners-in-your-enterprise.md @@ -119,6 +119,33 @@ You can use the following {% data variables.product.prodname_cli %} commands to 1. To disable a network configuration, to the right of the network configuration, click {% octicon "kebab-horizontal" aria-label="Menu" %}. Then click **Disable**. 1. To delete a network configuration, to the right of the network configuration, click {% octicon "kebab-horizontal" aria-label="Menu" %}. Then click **Delete**. +### 5. Optionally, add a failover network to a network configuration + +{% data reusables.actions.azure-vnet-about-failover %} + +Before adding a failover network, ensure you have configured the Azure resources (VNET, subnet, network security group, and network settings resource) for the secondary subnet, following the same "Configuring your Azure resources" procedures above. The failover subnet can be in a different Azure region from your primary subnet. + +{% data reusables.enterprise-accounts.access-enterprise %} +{% data reusables.enterprise-accounts.settings-tab %} +1. In the left sidebar, click **Hosted compute networking**. +1. Click the edit icon ({% octicon "pencil" aria-label="Edit a network configuration" %}) next to the network configuration you want to add a failover network to. Then click **Edit configuration**. +1. Click **Add failover network**. +1. In the popup window, enter the network settings resource ID for your secondary (failover) Azure subnet. +1. Click **Add Azure Virtual Network**. +1. You will now see two subnets listed in the network configuration: the primary and the failover, labeled accordingly. + +### 6. Optionally, enable or disable the failover network + +After adding a failover network, you can enable it to route traffic through the secondary subnet, or disable it to return to the primary subnet. + +{% data reusables.enterprise-accounts.access-enterprise %} +{% data reusables.enterprise-accounts.settings-tab %} +1. In the left sidebar, click **Hosted compute networking**. +1. Click the edit icon ({% octicon "pencil" aria-label="Edit a network configuration" %}) next to the network configuration. Then click **Edit configuration**. +1. To switch to the failover network, click **Enable failover VNET**. Runner traffic will be routed through the failover subnet. +1. To switch back to the primary network, click **Disable failover VNET**. Runner traffic will return to the primary subnet. + + ## Enabling creation of network configurations for organizations You can allow organization owners in an enterprise to create their own organization-level network configurations. diff --git a/content/code-security/concepts/supply-chain-security/about-the-dependabot-yml-file.md b/content/code-security/concepts/supply-chain-security/about-the-dependabot-yml-file.md index 41b0c4932d1b..8f08572128c9 100644 --- a/content/code-security/concepts/supply-chain-security/about-the-dependabot-yml-file.md +++ b/content/code-security/concepts/supply-chain-security/about-the-dependabot-yml-file.md @@ -43,7 +43,7 @@ The `dependabot.yml` file controls how {% data variables.product.prodname_depend ## Where to store the `dependabot.yml` file -You must store this file in the `.github` directory of your repository in the default branch (typically `main`). The path is: `.github/dependabot.yml`. +You must store this file in the `.github` directory of your repository in the default branch (typically `main`), at `.github/dependabot.yml` or `.github/dependabot.yaml`. ## How the `dependabot.yml` file works diff --git a/content/copilot/concepts/agents/about-third-party-agents.md b/content/copilot/concepts/agents/about-third-party-agents.md index bd11ffa90004..46f08f94a05c 100644 --- a/content/copilot/concepts/agents/about-third-party-agents.md +++ b/content/copilot/concepts/agents/about-third-party-agents.md @@ -36,7 +36,7 @@ Before you can assign tasks to coding agents on {% data variables.product.github * For **{% data variables.copilot.copilot_pro %} and {% data variables.copilot.copilot_pro_plus %} subscribers**, see [AUTOTITLE](/copilot/how-tos/manage-your-account/manage-policies#enabling-or-disabling-third-party-agents-in-your-repositories). * For **{% data variables.copilot.copilot_for_business %} and {% data variables.copilot.copilot_enterprise %} subscribers**, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-organization/manage-policies) or [AUTOTITLE](/enterprise-cloud@latest/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-enterprise-policies). -These policies do not apply to **local** agents in {% data variables.product.prodname_vscode %}, which cannot be disabled. They _do_ apply to **cloud** agents in {% data variables.product.prodname_vscode %}. See [Types of agents](https://code.visualstudio.com/docs/copilot/agents/overview#_types-of-agents) in the {% data variables.product.prodname_vscode %} documentation. +These policies do not apply to **local** agents in {% data variables.product.prodname_vscode %}. To configure agent settings in {% data variables.product.prodname_vscode %}, see [Types of agents](https://code.visualstudio.com/docs/copilot/agents/overview#_types-of-agents) in the {% data variables.product.prodname_vscode %} documentation. To adjust enterprise agent settings in {% data variables.product.prodname_vscode %}, see [Enable or disable the use of agents](https://code.visualstudio.com/docs/enterprise/ai-settings#_enable-or-disable-the-use-of-agents) in the {% data variables.product.prodname_vscode %} documentation. ## Supported coding agents diff --git a/content/copilot/concepts/agents/coding-agent/about-coding-agent.md b/content/copilot/concepts/agents/coding-agent/about-coding-agent.md index 4b41b3035d89..b0ef38de3882 100644 --- a/content/copilot/concepts/agents/coding-agent/about-coding-agent.md +++ b/content/copilot/concepts/agents/coding-agent/about-coding-agent.md @@ -151,7 +151,7 @@ You can customize {% data variables.copilot.copilot_coding_agent %} in a number ### Limitations in {% data variables.copilot.copilot_coding_agent %}'s compatibility with other features -* **{% data variables.product.prodname_copilot_short %} isn't able to comply with certain rules that may be configured for your repository**. If you have configured a ruleset or branch protection rule that isn't compatible with {% data variables.copilot.copilot_coding_agent %} (for example the "Require signed commits" rule), access to the agent will be blocked. If the rule is configured using rulesets, you can add {% data variables.product.prodname_copilot_short %} as a bypass actor to enable access. See [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#granting-bypass-permissions-for-your-branch-or-tag-ruleset). +* **{% data variables.product.prodname_copilot_short %} isn't able to comply with certain rules that may be configured for your repository**. If you have configured a ruleset or branch protection rule that isn't compatible with {% data variables.copilot.copilot_coding_agent %}, access to the agent will be blocked. For example, a rule that only allows specific commit authors can prevent {% data variables.copilot.copilot_coding_agent %} from creating or updating pull requests. If the rule is configured using rulesets, you can add {% data variables.product.prodname_copilot_short %} as a bypass actor to enable access. See [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#granting-bypass-permissions-for-your-branch-or-tag-ruleset). * **{% data variables.copilot.copilot_coding_agent %} doesn't account for content exclusions**. Content exclusions allow administrators to configure {% data variables.product.prodname_copilot_short %} to ignore certain files. When using {% data variables.copilot.copilot_coding_agent %}, {% data variables.product.prodname_copilot_short %} will not ignore these files, and will be able to see and update them. See [AUTOTITLE](/copilot/managing-copilot/configuring-and-auditing-content-exclusion/excluding-content-from-github-copilot). * **{% data variables.copilot.copilot_coding_agent %} only works with repositories hosted on {% data variables.product.github %}**. If your repository is stored using a different code hosting platform, {% data variables.product.prodname_copilot_short %} won't be able to work on it. diff --git a/content/copilot/concepts/agents/coding-agent/risks-and-mitigations.md b/content/copilot/concepts/agents/coding-agent/risks-and-mitigations.md index 2bab420f37c6..5c8296182521 100644 --- a/content/copilot/concepts/agents/coding-agent/risks-and-mitigations.md +++ b/content/copilot/concepts/agents/coding-agent/risks-and-mitigations.md @@ -56,3 +56,4 @@ To mitigate this risk, {% data variables.copilot.copilot_coding_agent %} is desi * {% data variables.copilot.copilot_coding_agent %}'s commits are authored by {% data variables.product.prodname_copilot_short %}, with the developer who assigned the issue or requested the change to the pull request marked as the co-author. This makes it easier to identify code generated by {% data variables.copilot.copilot_coding_agent %} and who started the task. * Session logs and audit log events are available to administrators. * The commit message for each agent-authored commit includes a link to the agent session logs, for code review and auditing. See [AUTOTITLE](/copilot/how-tos/use-copilot-agents/coding-agent/track-copilot-sessions). +* {% data variables.copilot.copilot_coding_agent %}'s commits are signed, so they appear as "Verified" on {% data variables.product.github %}. This provides confidence that the commits were made by {% data variables.copilot.copilot_coding_agent %} and have not been altered. diff --git a/content/copilot/how-tos/administer-copilot/manage-for-organization/manage-policies.md b/content/copilot/how-tos/administer-copilot/manage-for-organization/manage-policies.md index 930793c7afe4..b307dedeb89d 100644 --- a/content/copilot/how-tos/administer-copilot/manage-for-organization/manage-policies.md +++ b/content/copilot/how-tos/administer-copilot/manage-for-organization/manage-policies.md @@ -45,6 +45,7 @@ category: > [!NOTE] > * {% data reusables.gated-features.third-party-agents %} +> * If your organization is owned by an enterprise, these settings are only visible if an enterprise admin has enabled third-party coding agents at the enterprise level. See [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-enterprise-policies). You can choose whether to allow the following coding agents to be enabled in your organization: diff --git a/content/copilot/how-tos/copilot-sdk/authenticate-copilot-sdk/bring-your-own-key.md b/content/copilot/how-tos/copilot-sdk/authenticate-copilot-sdk/bring-your-own-key.md index b73e11aecbf3..f2ead5fb8bf9 100644 --- a/content/copilot/how-tos/copilot-sdk/authenticate-copilot-sdk/bring-your-own-key.md +++ b/content/copilot/how-tos/copilot-sdk/authenticate-copilot-sdk/bring-your-own-key.md @@ -56,7 +56,7 @@ Azure AI Foundry (formerly Azure OpenAI) is a common BYOK deployment target for Replace `YOUR-RESOURCE` with your Azure resource name and `YOUR-DEPLOYMENT-NAME` with your model deployment name. Set the `FOUNDRY_API_KEY` environment variable to your Azure API key. -For examples in Python, Go, and .NET, see [BYOK](https://github.com/github/copilot-sdk/blob/main/docs/auth/byok.md) in the `github/copilot-sdk` repository. +For examples in Python, Go, and .NET, see [BYOK](https://github.com/github/copilot-sdk/blob/main/docs/auth/byok.md) in the `github/copilot-sdk` repository. {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Provider configuration reference @@ -227,7 +227,7 @@ const client = new CopilotClient({ Results are cached after the first call. The handler completely replaces the CLI's `models.list` RPC—no fallback to the server occurs. -For examples in Python, Go, and .NET, see [BYOK](https://github.com/github/copilot-sdk/blob/main/docs/auth/byok.md) in the `github/copilot-sdk` repository. +For examples in Python, Go, and .NET, see [BYOK](https://github.com/github/copilot-sdk/blob/main/docs/auth/byok.md) in the `github/copilot-sdk` repository. {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Limitations diff --git a/content/copilot/how-tos/copilot-sdk/observability/opentelemetry.md b/content/copilot/how-tos/copilot-sdk/observability/opentelemetry.md index 5dd098536f25..a1c19b329ecb 100644 --- a/content/copilot/how-tos/copilot-sdk/observability/opentelemetry.md +++ b/content/copilot/how-tos/copilot-sdk/observability/opentelemetry.md @@ -14,7 +14,7 @@ category: {% data variables.copilot.copilot_sdk %} has built-in support for configuring OpenTelemetry on the CLI process and propagating W3C Trace Context between the SDK and CLI. -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/observability/opentelemetry.md). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/observability/opentelemetry.md). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Built-in telemetry support diff --git a/content/copilot/how-tos/copilot-sdk/set-up-copilot-sdk/choosing-a-setup-path.md b/content/copilot/how-tos/copilot-sdk/set-up-copilot-sdk/choosing-a-setup-path.md index e043af62b5da..797b84b0d8a2 100644 --- a/content/copilot/how-tos/copilot-sdk/set-up-copilot-sdk/choosing-a-setup-path.md +++ b/content/copilot/how-tos/copilot-sdk/set-up-copilot-sdk/choosing-a-setup-path.md @@ -90,6 +90,7 @@ All guides assume you have: * Python: `pip install github-copilot-sdk` * Go: `go get github.com/github/copilot-sdk/go` * .NET: `dotnet add package GitHub.Copilot.SDK` + * Java: See the [`github/copilot-sdk-java`](https://github.com/github/copilot-sdk-java) repository for Maven/Gradle setup If you're new to the {% data variables.copilot.copilot_sdk %}, start with [AUTOTITLE](/copilot/how-tos/copilot-sdk/sdk-getting-started) first, then return here for production configuration. diff --git a/content/copilot/how-tos/copilot-sdk/troubleshooting/debug-copilot-sdk.md b/content/copilot/how-tos/copilot-sdk/troubleshooting/debug-copilot-sdk.md index a94f5858fe68..3e4af8632131 100644 --- a/content/copilot/how-tos/copilot-sdk/troubleshooting/debug-copilot-sdk.md +++ b/content/copilot/how-tos/copilot-sdk/troubleshooting/debug-copilot-sdk.md @@ -22,7 +22,7 @@ const client = new CopilotClient({ }); ``` -For examples in Python, Go, and .NET, see [Debugging Guide](https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting/debugging.md) in the `github/copilot-sdk` repository. +For examples in Python, Go, and .NET, see [Debugging Guide](https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting/debugging.md) in the `github/copilot-sdk` repository. {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Log directory @@ -60,7 +60,7 @@ For Python and Go, which do not currently support passing extra CLI arguments, r }); ``` - For examples in Python, Go, and .NET, see [Debugging Guide](https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting/debugging.md) in the `github/copilot-sdk` repository. + For examples in Python, Go, and .NET, see [Debugging Guide](https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting/debugging.md) in the `github/copilot-sdk` repository. {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### "Not authenticated" @@ -82,7 +82,7 @@ For Python and Go, which do not currently support passing extra CLI arguments, r }); ``` - For examples in Python, Go, and .NET, see [Debugging Guide](https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting/debugging.md) in the `github/copilot-sdk` repository. + For examples in Python, Go, and .NET, see [Debugging Guide](https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting/debugging.md) in the `github/copilot-sdk` repository. {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### "Session not found" diff --git a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/custom-agents.md b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/custom-agents.md index 17f5554c0b1b..2ba0a2b84faa 100644 --- a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/custom-agents.md +++ b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/custom-agents.md @@ -53,7 +53,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#defining-custom-agents). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#defining-custom-agents). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Configuration reference @@ -96,7 +96,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#selecting-an-agent-at-session-creation). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#selecting-an-agent-at-session-creation). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## How sub-agent delegation works @@ -172,7 +172,7 @@ const response = await session.sendAndWait({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#listening-to-sub-agent-events). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#listening-to-sub-agent-events). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Building an agent tree UI diff --git a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/custom-skills.md b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/custom-skills.md index 2eacc2a73e7c..37df6646d757 100644 --- a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/custom-skills.md +++ b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/custom-skills.md @@ -43,7 +43,7 @@ const session = await client.createSession({ await session.sendAndWait({ prompt: "Review this code for security issues" }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/skills.md#loading-skills). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/skills.md#loading-skills). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Disabling skills @@ -56,7 +56,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/skills.md#disabling-skills). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/skills.md#disabling-skills). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Skill directory structure @@ -116,6 +116,8 @@ When you call `createSession()`, pass these skill-related fields in the session | Go | `DisabledSkills` | `[]string` | Skills to disable | | .NET | `SkillDirectories` | `List` | Directories to load skills from | | .NET | `DisabledSkills` | `List` | Skills to disable | +| Java | `skillDirectories` | `List` | Directories to load skills from | +| Java | `disabledSkills` | `List` | Skills to disable | ## Best practices diff --git a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/image-input.md b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/image-input.md index 81f26b185ba3..f0977f71ec04 100644 --- a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/image-input.md +++ b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/image-input.md @@ -53,7 +53,7 @@ await session.send({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/image-input.md#quick-start--file-attachment). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/image-input.md#quick-start--file-attachment). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Quick start: blob attachment @@ -84,7 +84,7 @@ await session.send({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/image-input.md#quick-start--blob-attachment). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/image-input.md#quick-start--blob-attachment). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Supported formats diff --git a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/mcp-servers.md b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/mcp-servers.md index 0f2ae67e0c6a..19f57db92ab0 100644 --- a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/mcp-servers.md +++ b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/mcp-servers.md @@ -57,7 +57,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/mcp.md#configuration). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/mcp.md#configuration). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Quick start: filesystem MCP server diff --git a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/session-persistence.md b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/session-persistence.md index 821fc7253efb..e7608218726d 100644 --- a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/session-persistence.md +++ b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/session-persistence.md @@ -43,7 +43,7 @@ await session.sendAndWait({ prompt: "Analyze my codebase" }); // You can safely close the client ``` -For examples in Python, Go, and C#, see the `github/copilot-sdk` [repository](https://github.com/github/copilot-sdk/blob/main/docs/features/session-persistence.md#quick-start-creating-a-resumable-session). +For examples in Python, Go, and C#, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/session-persistence.md#quick-start-creating-a-resumable-session). For Java, see the [`github/copilot-sdk-java` repository](https://github.com/github/copilot-sdk-java). ## Resuming a session @@ -57,7 +57,7 @@ const session = await client.resumeSession("user-123-task-456"); await session.sendAndWait({ prompt: "What did we discuss earlier?" }); ``` -For examples in Python, Go, and C#, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/session-persistence.md#resuming-a-session). +For examples in Python, Go, and C#, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/session-persistence.md#resuming-a-session). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Resume options @@ -173,7 +173,7 @@ const sessionId = createSessionId("alice", "code-review"); // → "alice-code-review-1706932800000" ``` -For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/session-persistence.md#example-generating-session-ids). +For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/session-persistence.md#example-generating-session-ids). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Managing session lifecycle @@ -238,6 +238,7 @@ Each SDK provides idiomatic automatic cleanup patterns: | **Python** | `async with` context manager | `async with await client.create_session(config) as session:` | | **C#** | `IAsyncDisposable` | `await using var session = await client.CreateSessionAsync(config);` | | **Go** | `defer` | `defer session.Disconnect()` | +| **Java** | `try-with-resources` | `try (var session = client.createSession(config).get()) { ... }` | > [!NOTE] > `destroy()` has been replaced by `disconnect()` and will be removed in a future release. Existing code using `destroy()` will continue to work but should be migrated. diff --git a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/steering-and-queueing.md b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/steering-and-queueing.md index 275fb0a566bb..c0fe8d837569 100644 --- a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/steering-and-queueing.md +++ b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/steering-and-queueing.md @@ -50,7 +50,7 @@ await session.send({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/steering-and-queueing.md#steering-immediate-mode). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/steering-and-queueing.md#steering-immediate-mode). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### How steering works internally @@ -94,7 +94,7 @@ await session.send({ // Messages are processed in FIFO order after each turn completes ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/steering-and-queueing.md#queueing-enqueue-mode). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/steering-and-queueing.md#queueing-enqueue-mode). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### How queueing works internally @@ -130,7 +130,7 @@ await session.send({ }); ``` -For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/steering-and-queueing.md#combining-steering-and-queueing). +For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/steering-and-queueing.md#combining-steering-and-queueing). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Choosing between steering and queueing diff --git a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/streaming-events.md b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/streaming-events.md index 0e8de2614a00..16d2935b4e0e 100644 --- a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/streaming-events.md +++ b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/streaming-events.md @@ -50,7 +50,7 @@ session.on("assistant.message_delta", (event) => { }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/streaming-events.md#subscribing-to-events). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/streaming-events.md#subscribing-to-events). {% data reusables.copilot.copilot-sdk.java-sdk-link %} > [!TIP] > **Python / Go:** These SDKs use a single `Data` class/struct with all possible fields as optional/nullable. Only the fields listed in the tables below are populated for each event type—the rest will be `None` / `nil`. @@ -58,6 +58,8 @@ For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository]( > **.NET:** The .NET SDK uses separate, strongly-typed data classes per event (for example, `AssistantMessageDeltaData`), so only the relevant fields exist on each type. > > **TypeScript:** The TypeScript SDK uses a discriminated union—when you match on `event.type`, the `data` payload is automatically narrowed to the correct shape. +> +> **Java:** The Java SDK uses typed event classes (for example, `AssistantMessageEvent`, `SessionIdleEvent`) with typed data accessors. ## Assistant events diff --git a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/working-with-hooks.md b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/working-with-hooks.md index cea907137782..57650d8c9caa 100644 --- a/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/working-with-hooks.md +++ b/content/copilot/how-tos/copilot-sdk/use-copilot-sdk/working-with-hooks.md @@ -50,7 +50,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#registering-hooks). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#registering-hooks). {% data reusables.copilot.copilot-sdk.java-sdk-link %} > [!TIP] > Every hook handler receives an `invocation` parameter containing the `sessionId`, which is useful for correlating logs and maintaining per-session state. @@ -81,7 +81,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#allow-list-a-safe-set-of-tools). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#allow-list-a-safe-set-of-tools). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Restrict file access to specific directories @@ -207,7 +207,7 @@ const session = await client.createSession({ }); ``` -For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#structured-audit-log). +For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#structured-audit-log). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Redact secrets from tool results @@ -265,7 +265,7 @@ const session = await client.createSession({ }); ``` -For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#desktop-notification-on-session-events). +For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#desktop-notification-on-session-events). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Play a sound when a tool finishes @@ -454,7 +454,7 @@ const session = await client.createSession({ }); ``` -For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#session-metrics). +For an example in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md#session-metrics). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Combining hooks diff --git a/content/copilot/how-tos/copilot-sdk/use-hooks/error-handling.md b/content/copilot/how-tos/copilot-sdk/use-hooks/error-handling.md index 3f82d01205f8..ab15a7f000fc 100644 --- a/content/copilot/how-tos/copilot-sdk/use-hooks/error-handling.md +++ b/content/copilot/how-tos/copilot-sdk/use-hooks/error-handling.md @@ -31,7 +31,7 @@ type ErrorOccurredHandler = ( >; ``` -For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/error-handling.md#hook-signature). +For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/error-handling.md#hook-signature). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Input @@ -80,7 +80,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/error-handling.md#basic-error-logging). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/error-handling.md#basic-error-logging). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Send errors to monitoring service diff --git a/content/copilot/how-tos/copilot-sdk/use-hooks/post-tool-use.md b/content/copilot/how-tos/copilot-sdk/use-hooks/post-tool-use.md index 4e350998663f..aff7c8900d5d 100644 --- a/content/copilot/how-tos/copilot-sdk/use-hooks/post-tool-use.md +++ b/content/copilot/how-tos/copilot-sdk/use-hooks/post-tool-use.md @@ -29,7 +29,7 @@ type PostToolUseHandler = ( ) => Promise; ``` -For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/post-tool-use.md#hook-signature). +For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/post-tool-use.md#hook-signature). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Input @@ -76,7 +76,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/post-tool-use.md#log-all-tool-results). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/post-tool-use.md#log-all-tool-results). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Redact sensitive data diff --git a/content/copilot/how-tos/copilot-sdk/use-hooks/pre-tool-use.md b/content/copilot/how-tos/copilot-sdk/use-hooks/pre-tool-use.md index 423571dbf41a..441b4234e864 100644 --- a/content/copilot/how-tos/copilot-sdk/use-hooks/pre-tool-use.md +++ b/content/copilot/how-tos/copilot-sdk/use-hooks/pre-tool-use.md @@ -29,7 +29,7 @@ type PreToolUseHandler = ( ) => Promise; ``` -For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/pre-tool-use.md#hook-signature). +For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/pre-tool-use.md#hook-signature). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Input @@ -81,7 +81,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/pre-tool-use.md#allow-all-tools-logging-only). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/pre-tool-use.md#allow-all-tools-logging-only). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Block specific tools diff --git a/content/copilot/how-tos/copilot-sdk/use-hooks/quickstart.md b/content/copilot/how-tos/copilot-sdk/use-hooks/quickstart.md index 4003799fafed..2fe9747790c4 100644 --- a/content/copilot/how-tos/copilot-sdk/use-hooks/quickstart.md +++ b/content/copilot/how-tos/copilot-sdk/use-hooks/quickstart.md @@ -64,7 +64,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/index.md#quick-start). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/index.md#quick-start). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Hook invocation context diff --git a/content/copilot/how-tos/copilot-sdk/use-hooks/session-lifecycle.md b/content/copilot/how-tos/copilot-sdk/use-hooks/session-lifecycle.md index 4973d8ae1a07..776d28b6c29c 100644 --- a/content/copilot/how-tos/copilot-sdk/use-hooks/session-lifecycle.md +++ b/content/copilot/how-tos/copilot-sdk/use-hooks/session-lifecycle.md @@ -35,7 +35,7 @@ type SessionStartHandler = ( >; ``` -For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md#hook-signature). +For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md#hook-signature). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Input @@ -84,7 +84,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md#add-project-context-at-start). +For examples in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md#add-project-context-at-start). {% data reusables.copilot.copilot-sdk.java-sdk-link %} #### Handle session resume @@ -173,7 +173,7 @@ type SessionEndHandler = ( >; ``` -For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md#hook-signature-1). +For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md#hook-signature-1). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Input @@ -246,7 +246,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md#track-session-metrics). +For examples in Python, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md#track-session-metrics). {% data reusables.copilot.copilot-sdk.java-sdk-link %} #### Clean up resources diff --git a/content/copilot/how-tos/copilot-sdk/use-hooks/user-prompt-submitted.md b/content/copilot/how-tos/copilot-sdk/use-hooks/user-prompt-submitted.md index 0de451dfb723..b6ea560208cc 100644 --- a/content/copilot/how-tos/copilot-sdk/use-hooks/user-prompt-submitted.md +++ b/content/copilot/how-tos/copilot-sdk/use-hooks/user-prompt-submitted.md @@ -31,7 +31,7 @@ type UserPromptSubmittedHandler = ( >; ``` -For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/user-prompt-submitted.md#hook-signature). +For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/user-prompt-submitted.md#hook-signature). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ## Input @@ -71,7 +71,7 @@ const session = await client.createSession({ }); ``` -For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/user-prompt-submitted.md#log-all-user-prompts). +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/user-prompt-submitted.md#log-all-user-prompts). {% data reusables.copilot.copilot-sdk.java-sdk-link %} ### Add project context diff --git a/content/copilot/how-tos/use-copilot-agents/coding-agent/track-copilot-sessions.md b/content/copilot/how-tos/use-copilot-agents/coding-agent/track-copilot-sessions.md index a60d528b7098..9e2f8bb89502 100644 --- a/content/copilot/how-tos/use-copilot-agents/coding-agent/track-copilot-sessions.md +++ b/content/copilot/how-tos/use-copilot-agents/coding-agent/track-copilot-sessions.md @@ -123,9 +123,11 @@ You can see a list of your running and past pull requests generated by agents in ## Tracing commits to session logs -Every commit from {% data variables.copilot.copilot_coding_agent %} is authored by {% data variables.product.prodname_copilot_short %}, with the human who started the task marked as the co-author. Each commit message includes a link to the session logs for that commit. +Commits from {% data variables.copilot.copilot_coding_agent %} have the following characteristics: -This gives you a permanent link from any agent-authored commit back to the full session logs, so you can understand why {% data variables.product.prodname_copilot_short %} made a change during code review or trace it later for auditing purposes. +* Every commit is authored by {% data variables.product.prodname_copilot_short %}, with the human who started the task marked as the co-author. +* Each commit message includes a link to the session logs for that commit, so you can understand why {% data variables.product.prodname_copilot_short %} made a change during code review or trace it later for auditing purposes. +* Commits from {% data variables.copilot.copilot_coding_agent %} are signed and appear as "Verified" on {% data variables.product.github %}. ## Using the session logs to understand {% data variables.product.prodname_copilot_short %}'s approach diff --git a/content/copilot/responsible-use/copilot-coding-agent.md b/content/copilot/responsible-use/copilot-coding-agent.md index 441388aaf826..917fe5445e9d 100644 --- a/content/copilot/responsible-use/copilot-coding-agent.md +++ b/content/copilot/responsible-use/copilot-coding-agent.md @@ -130,6 +130,8 @@ Its permissions are limited, allowing it to push code and read other resources. {% data variables.copilot.copilot_coding_agent %}'s commits are authored by {% data variables.product.prodname_copilot_short %}, with the human who started the task marked as the co-author. This makes it easier to identify code generated by the agent and who initiated the task. +{% data variables.copilot.copilot_coding_agent %}'s commits are signed, so they appear as "Verified" on {% data variables.product.github %}. This provides confidence that the commits were made by {% data variables.copilot.copilot_coding_agent %} and have not been altered. + Each commit message includes a link to the agent session logs. This gives you a permanent link from any agent-authored commit to the full session logs, so you can understand why {% data variables.product.prodname_copilot_short %} made a change during code review or trace it later for auditing purposes. ### Preventing data exfiltration diff --git a/content/organizations/managing-organization-settings/about-azure-private-networking-for-github-hosted-runners-in-your-organization.md b/content/organizations/managing-organization-settings/about-azure-private-networking-for-github-hosted-runners-in-your-organization.md index c06abc117a8b..a1159253a623 100644 --- a/content/organizations/managing-organization-settings/about-azure-private-networking-for-github-hosted-runners-in-your-organization.md +++ b/content/organizations/managing-organization-settings/about-azure-private-networking-for-github-hosted-runners-in-your-organization.md @@ -38,6 +38,12 @@ category: {% data reusables.actions.azure-vnet-networking-policies %} +## About VNET failover + +{% data reusables.actions.azure-vnet-about-failover %} + +For more information about configuring a failover network, see [AUTOTITLE](/organizations/managing-organization-settings/configuring-private-networking-for-github-hosted-runners-in-your-organization#5-optionally-add-a-failover-network-to-a-network-configuration). + ## Using {% data variables.product.company_short %}-hosted runners with an Azure VNET {% data reusables.actions.azure-vnet-next-steps-links %} diff --git a/content/organizations/managing-organization-settings/configuring-private-networking-for-github-hosted-runners-in-your-organization.md b/content/organizations/managing-organization-settings/configuring-private-networking-for-github-hosted-runners-in-your-organization.md index 3b424ac90a95..5b159367ca89 100644 --- a/content/organizations/managing-organization-settings/configuring-private-networking-for-github-hosted-runners-in-your-organization.md +++ b/content/organizations/managing-organization-settings/configuring-private-networking-for-github-hosted-runners-in-your-organization.md @@ -108,6 +108,35 @@ https://api.github.com/graphql 1. To disable a network configuration, to the right of the network configuration, click {% octicon "kebab-horizontal" aria-label="Menu" %}. Then click **Disable**. 1. To delete a network configuration, to the right of the network configuration, click {% octicon "kebab-horizontal" aria-label="Menu" %}. Then click **Delete**. +### 5. Optionally, add a failover network to a network configuration + +{% data reusables.actions.azure-vnet-about-failover %} + +Before adding a failover network, ensure you have configured the Azure resources (VNET, subnet, network security group, and network settings resource) for the secondary subnet, following the same "Configuring your Azure resources" procedures above. The failover subnet can be in a different Azure region from your primary subnet. + +{% data reusables.profile.access_org %} +{% data reusables.profile.org_settings %} +1. In the left sidebar, click **Hosted compute networking**. +1. Click the edit icon ({% octicon "pencil" aria-label="Edit a network configuration" %}) next to the network configuration you want to add a failover network to. Then click **Edit configuration**. +1. Click **Add failover network**. +1. In the popup window, enter the network settings resource ID for your secondary (failover) Azure subnet. +1. Click **Add Azure Virtual Network**. +1. You will now see two subnets listed in the network configuration: the primary and the failover, labeled accordingly. + +### 6. Optionally, enable or disable the failover network + +After adding a failover network, you can enable it to route traffic through the secondary subnet, or disable it to return to the primary subnet. + +{% data reusables.profile.access_org %} +{% data reusables.profile.org_settings %} +1. In the left sidebar, click **Hosted compute networking**. +1. Click the edit icon ({% octicon "pencil" aria-label="Edit a network configuration" %}) next to the network configuration. Then click **Edit configuration**. +1. To switch to the failover network, click **Enable failover VNET**. Runner traffic will be routed through the failover subnet. +1. To switch back to the primary network, click **Disable failover VNET**. Runner traffic will return to the primary subnet. + +> [!NOTE] +> If {% data variables.product.company_short %} enables failover automatically during a regional outage, you will be notified via audit log event and email. You must manually disable the failover to return to the primary subnet when the outage is resolved. + ## Deleting a subnet {% data reusables.actions.azure-vnet-deleting-a-subnet %} diff --git a/data/reusables/actions/azure-vnet-about-failover.md b/data/reusables/actions/azure-vnet-about-failover.md new file mode 100644 index 000000000000..dc87c2ec9c33 --- /dev/null +++ b/data/reusables/actions/azure-vnet-about-failover.md @@ -0,0 +1,11 @@ +> [!NOTE] +> VNET failover is in {% data variables.release-phases.public_preview %} and subject to change. + +You can configure a failover network for your network configuration. A failover network is a secondary Azure Virtual Network subnet, which can be in a different Azure region from your primary subnet. If your primary subnet becomes unavailable due to a regional outage or other disruption, you can enable the failover network to route runner traffic through the secondary subnet, maintaining continuity for your {% data variables.product.prodname_actions %} workflows. + +Key points about VNET failover: + +* The failover subnet can reside in a different Azure region than the primary subnet. +* Switching between primary and failover subnets is a manual process. You enable or disable the failover network at your discretion. +* Both the primary and failover subnets must be configured with the required Azure resources (VNET/subnet, network settings, etc.) before you can use failover. +* The failover subnet must be in a [supported region](/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/about-azure-private-networking-for-github-hosted-runners-in-your-enterprise#about-supported-regions). diff --git a/data/reusables/actions/azure-vnet-hosted-compute-troubleshooting.md b/data/reusables/actions/azure-vnet-hosted-compute-troubleshooting.md index 77576994ebf8..f3eb2e5952d0 100644 --- a/data/reusables/actions/azure-vnet-hosted-compute-troubleshooting.md +++ b/data/reusables/actions/azure-vnet-hosted-compute-troubleshooting.md @@ -105,3 +105,31 @@ If you experience this error, you can see more information by running the comman ### Network settings configured at the wrong level If network settings were configured using an organization's `databaseId` instead of an enterprise `databaseId`, an error will occur. The error message will indicate that a private network cannot be established with the provided resource ID because it is already associated with a different enterprise or organization. To resolve this, delete the existing network settings and recreate them using the enterprise `databaseId`. + +### Failover network not switching traffic + +> [!NOTE] +> VNET failover is in {% data variables.release-phases.public_preview %} and subject to change. +> [!IMPORTANT] +> Switching between the primary and failover networks is a gradual process. During the transition, runners may be running on both networks simultaneously. Based on testing, the full transition takes approximately 15 minutes. Ensure both subnets remain accessible during this period. + +If enabling the failover network does not appear to reroute runner traffic, check the following: + +* Ensure the failover subnet's Azure resources (VNET/subnet, NSG/firewall, network settings) are correctly configured. Follow the same "Configuring your Azure resources" procedures used for your primary subnet. +* Confirm the failover network was added to the correct network configuration and that the configuration is associated with the appropriate runner group. + +### Failover subnet not reachable + +If runners cannot connect after enabling the failover network, the issue is likely with the Azure resources configured for the failover subnet. + +* Ensure the failover subnet has the correct NSG or firewall rules applied, matching the requirements listed in the [AUTOTITLE](/enterprise-cloud@latest/admin/configuring-settings/configuring-private-networking-for-hosted-compute-products/configuring-private-networking-for-github-hosted-runners-in-your-enterprise) procedures. +* Verify that the failover subnet has sufficient IP address space for the expected runner concurrency. + +### Cannot switch back to primary after GitHub-initiated failover + + +1. Navigate to your network configuration in the **Hosted compute networking** settings. +1. Click the edit icon next to the network configuration. Then click **Edit configuration**. +1. Click **Disable failover VNET** to return runner traffic to the primary subnet. + +If you are unable to disable the failover, ensure the primary subnet's Azure resources are healthy and accessible. Verify there are no ongoing outages in the primary subnet's Azure region. diff --git a/data/reusables/copilot/copilot-sdk/java-sdk-link.md b/data/reusables/copilot/copilot-sdk/java-sdk-link.md new file mode 100644 index 000000000000..248b9ade963a --- /dev/null +++ b/data/reusables/copilot/copilot-sdk/java-sdk-link.md @@ -0,0 +1 @@ +For Java, see the [`github/copilot-sdk-java` repository](https://github.com/github/copilot-sdk-java). \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0dd6ab2e5e8e..568b60d35631 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,6 @@ "cheerio": "^1.2.0", "classnames": "^2.5.1", "clsx": "^2.1.1", - "connect-datadog": "^0.0.9", "connect-timeout": "1.9.1", "cookie-parser": "^1.4.7", "cuss": "2.2.0", @@ -124,7 +123,6 @@ "@octokit/rest": "22.0.0", "@playwright/test": "^1.58.2", "@types/accept-language-parser": "1.5.7", - "@types/connect-datadog": "^0.0.10", "@types/connect-timeout": "1.9.0", "@types/cookie": "0.6.0", "@types/cookie-parser": "1.4.8", @@ -496,7 +494,6 @@ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -537,7 +534,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -574,7 +570,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", @@ -591,7 +586,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "license": "ISC", - "peer": true, "dependencies": { "yallist": "^3.0.2" } @@ -601,7 +595,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -633,7 +626,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", @@ -677,7 +669,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -687,7 +678,6 @@ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" @@ -2344,7 +2334,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -2573,6 +2562,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -2873,6 +2863,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -3780,6 +3771,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.1.tgz", "integrity": "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -5357,6 +5349,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5396,6 +5389,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5416,6 +5410,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5436,6 +5431,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5456,6 +5452,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5476,6 +5473,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5496,6 +5494,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5516,6 +5515,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5536,6 +5536,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5556,6 +5557,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5576,6 +5578,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5596,6 +5599,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5616,6 +5620,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5636,6 +5641,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5653,6 +5659,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, "license": "Apache-2.0", "optional": true, "bin": { @@ -5681,6 +5688,7 @@ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.58.2" }, @@ -6510,30 +6518,6 @@ "@types/node": "*" } }, - "node_modules/@types/connect-datadog": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@types/connect-datadog/-/connect-datadog-0.0.10.tgz", - "integrity": "sha512-zYNEyqNmdRyOR/8bWTW9qZAfhCxVpid4B0Yy5NCbzJWDS60Z7kur2rPvzDrRuSpZ1sn6gR/t/aZjIl6fv7gdIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*", - "hot-shots": "^6.3.0" - } - }, - "node_modules/@types/connect-datadog/node_modules/hot-shots": { - "version": "6.8.7", - "resolved": "https://registry.npmjs.org/hot-shots/-/hot-shots-6.8.7.tgz", - "integrity": "sha512-XH8iezBSZgVw2jegu96pUfF1Zv0VZ/iXjb7L5yE3F7mn7/bdhf4qeniXjO0wQWeefe433rhOsazNKLxM+XMI9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - }, - "optionalDependencies": { - "unix-dgram": "2.0.x" - } - }, "node_modules/@types/connect-timeout": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@types/connect-timeout/-/connect-timeout-1.9.0.tgz", @@ -6615,6 +6599,7 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -6824,6 +6809,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz", "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -6835,6 +6821,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -7015,6 +7002,7 @@ "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", @@ -7686,6 +7674,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7725,6 +7714,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8293,6 +8283,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", @@ -8989,30 +8980,6 @@ "version": "0.0.1", "license": "MIT" }, - "node_modules/connect-datadog": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/connect-datadog/-/connect-datadog-0.0.9.tgz", - "integrity": "sha512-CThcRgy6AAStdBRsrNdneeJkEuH0/2G1QanYFfl0F+ZkbQaRMMM9/apvZ1R3SMD9iAST/Xa78Q0jC51KCB/4LQ==", - "license": "MIT", - "dependencies": { - "hot-shots": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/connect-datadog/node_modules/hot-shots": { - "version": "6.8.7", - "resolved": "https://registry.npmjs.org/hot-shots/-/hot-shots-6.8.7.tgz", - "integrity": "sha512-XH8iezBSZgVw2jegu96pUfF1Zv0VZ/iXjb7L5yE3F7mn7/bdhf4qeniXjO0wQWeefe433rhOsazNKLxM+XMI9w==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - }, - "optionalDependencies": { - "unix-dgram": "2.0.x" - } - }, "node_modules/connect-timeout": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.9.1.tgz", @@ -9118,8 +9085,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", @@ -9992,6 +9958,7 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10052,6 +10019,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -10316,6 +10284,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -11267,6 +11236,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -11367,7 +11337,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -11565,6 +11534,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -12936,6 +12906,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -13080,7 +13051,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "license": "MIT", - "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -15232,6 +15202,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, "license": "MIT", "optional": true }, @@ -16030,6 +16001,7 @@ "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -16132,6 +16104,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -16304,6 +16277,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -16324,6 +16298,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -16351,7 +16326,8 @@ "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-markdown": { "version": "10.1.0", @@ -16975,6 +16951,7 @@ "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -17877,6 +17854,7 @@ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", @@ -18138,6 +18116,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -18470,6 +18449,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18817,6 +18797,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -19021,6 +19002,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -19129,6 +19111,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -19719,8 +19702,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/yaml": { "version": "2.8.3", diff --git a/package.json b/package.json index 13e2807409f5..b24a4c6afd17 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,6 @@ "cheerio": "^1.2.0", "classnames": "^2.5.1", "clsx": "^2.1.1", - "connect-datadog": "^0.0.9", "connect-timeout": "1.9.1", "cookie-parser": "^1.4.7", "cuss": "2.2.0", @@ -282,7 +281,6 @@ "@octokit/rest": "22.0.0", "@playwright/test": "^1.58.2", "@types/accept-language-parser": "1.5.7", - "@types/connect-datadog": "^0.0.10", "@types/connect-timeout": "1.9.0", "@types/cookie": "0.6.0", "@types/cookie-parser": "1.4.8", diff --git a/src/frame/lib/app.ts b/src/frame/lib/app.ts index b441f3fd997d..aa3506be7396 100644 --- a/src/frame/lib/app.ts +++ b/src/frame/lib/app.ts @@ -1,17 +1,8 @@ import express from 'express' import middleware from '@/frame/middleware' -import connect_datadog from 'connect-datadog' -import statsd from '@/observability/lib/statsd' function createApp() { const app = express() - app.use( - connect_datadog({ - dogstatsd: statsd, - method: true, - response_code: true, - }), - ) middleware(app) return app } diff --git a/src/frame/middleware/index.ts b/src/frame/middleware/index.ts index 9dc2fd5b6adc..1a19c09751e2 100644 --- a/src/frame/middleware/index.ts +++ b/src/frame/middleware/index.ts @@ -14,6 +14,7 @@ import { setLanguageFastlySurrogateKey, } from './set-fastly-surrogate-key' import handleErrors from '@/observability/middleware/handle-errors' +import expressMetrics from '@/observability/middleware/express-metrics' import handleNextDataPath from './handle-next-data-path' import detectLanguage from '@/languages/middleware/detect-language' import detectVersion from '@/versions/middleware/detect-version' @@ -115,6 +116,7 @@ export default function index(app: Express) { // *** Logging *** app.use(initLoggerContext) // Context for both inline logs (e.g. logger.info) and automatic logs app.use(getAutomaticRequestLogger()) // Automatic logging for all requests e.g. "GET /path 200" + app.use(expressMetrics) // StatsD metrics for response time and status codes // Put this early to make it as fast as possible because it's used // to check the health of each cluster. diff --git a/src/observability/middleware/express-metrics.ts b/src/observability/middleware/express-metrics.ts new file mode 100644 index 000000000000..e5c351776178 --- /dev/null +++ b/src/observability/middleware/express-metrics.ts @@ -0,0 +1,27 @@ +import type { Request, Response, NextFunction } from 'express' +import statsd from '@/observability/lib/statsd' + +const STAT = 'node.express.router' +const SANITIZE_RE = /[|,]/g + +// Replaces the deprecated `connect-datadog` package. +// Emits the same metric names so existing Datadog dashboards keep working. +export default function expressMetrics(req: Request, res: Response, next: NextFunction) { + const start = performance.now() + let emitted = false + const emit = () => { + if (emitted) return + emitted = true + const tags = [`method:${req.method.toLowerCase()}`, `response_code:${res.statusCode}`] + const route = req.route?.path + ? (req.baseUrl + String(req.route.path)).replace(SANITIZE_RE, '-') + : '' + if (route) tags.push(`route:${route}`) + statsd.histogram(`${STAT}.response_time`, performance.now() - start, 1, tags) + statsd.increment(`${STAT}.response_code.${res.statusCode}`, 1, tags) + statsd.increment(`${STAT}.response_code.all`, 1, tags) + } + res.once('finish', emit) + res.once('close', emit) + next() +}