diff --git a/AGENTS.md b/AGENTS.md index b1e6f61..0c0bb9b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ **Key Difference from create-task-action**: This action targets the Coder Agents Chat API (`/api/experimental/chats`) instead of the Tasks API. Agents purposefully does NOT expose template selection. It either auto-provisions a workspace or uses an existing one. **Tech Stack**: -- **Action Runtime**: Node 24 +- **Action Runtime**: Node 20 (GHES 3.16 runners reject `using: node24`) - **Development Runtime**: Bun (JavaScript/TypeScript runtime & bundler) - **Language**: TypeScript with strict mode enabled - **Validation**: Zod for runtime schema validation diff --git a/README.md b/README.md index 7ed7eb7..84d812e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,15 @@ The chat owner is always the user the `coder-token` belongs to. Read [Security m - Coder deployment with Agents enabled (experimental). - Coder session token belonging to the user the chats should run as. Treat this token as a high-value secret: anyone holding it acts as that Coder user via the agent's tool plane (see [Security model](#security-model)). +## Node runtime + +The action declares `runs.using: node20` instead of `node24`. This is deliberate: + +- GitHub Enterprise Server ships older runners that reject `using: node24` at job setup, before the action even loads. +- Current GitHub.com runners run `node20` actions on Node 24 automatically. Node 20 itself reached end-of-life in April 2026 and is being removed from GitHub-hosted runners, but actions declaring `node20` still load and simply run on Node 24. + +So one release works everywhere: new runners execute the action with Node 24, older GHES runners execute it with Node 20. To keep that true, the code avoids APIs that need anything newer than Node 20. + ## Quickstart Triage every issue labeled `coder`: diff --git a/action.yaml b/action.yaml index 957a8ca..e62c26e 100644 --- a/action.yaml +++ b/action.yaml @@ -123,5 +123,5 @@ outputs: description: "Human-readable error message when the chat fails." runs: - using: "node24" + using: "node20" main: "dist/index.js" diff --git a/dist/index.js b/dist/index.js index 36964a9..d85efbb 100644 --- a/dist/index.js +++ b/dist/index.js @@ -36793,10 +36793,13 @@ function sanitizeLabelToken(input) { // src/comment.ts var DEFAULT_GITHUB_SERVER_URL = "https://github.com"; var githubURLRegexCache = new Map; +function escapeRegExp(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} function githubURLRegex(serverURL) { let regex = githubURLRegexCache.get(serverURL); if (!regex) { - const base = RegExp.escape(normalizeBaseUrl(serverURL)); + const base = escapeRegExp(normalizeBaseUrl(serverURL)); regex = new RegExp(`^${base}/([^/]+)/([^/]+)/(?:issues|pull)/(\\d+)/?(?:[?#].*)?$`); githubURLRegexCache.set(serverURL, regex); } diff --git a/src/comment.test.ts b/src/comment.test.ts index 3c5ea8d..ad4ec2b 100644 --- a/src/comment.test.ts +++ b/src/comment.test.ts @@ -110,7 +110,7 @@ describe("parseGithubItemURL", () => { }); test("does not treat the server host as a regex pattern", () => { - // The dots in the host are metacharacters; RegExp.escape must keep + // The dots in the host are metacharacters; escapeRegExp must keep // them literal so a host that merely matches the pattern (dot as // wildcard) is still rejected. expect( diff --git a/src/comment.ts b/src/comment.ts index 13a1079..3bb5b4c 100644 --- a/src/comment.ts +++ b/src/comment.ts @@ -17,7 +17,7 @@ export const DEFAULT_GITHUB_SERVER_URL = "https://github.com"; // Anchored issue/PR URL matcher for `serverURL`, compiled once per server URL. // In production the server URL is fixed for the run, so this cache only ever // holds a single entry; it grows past one entry solely under tests that -// exercise multiple hosts. `RegExp.escape` neutralizes metacharacters in the +// exercise multiple hosts. `escapeRegExp` neutralizes metacharacters in the // host (e.g. the dots in `github.com`) so it matches literally. Anchored at // both ends so a non-server host or extra path segments // (e.g. `.../issues/123/files`, `https://attacker.example/owner/repo/issues/1`) @@ -27,10 +27,17 @@ export const DEFAULT_GITHUB_SERVER_URL = "https://github.com"; // comment). const githubURLRegexCache = new Map(); +// Escape regex metacharacters so the string matches literally. Hand-rolled +// because `RegExp.escape` requires Node 24+, while the action targets Node 20 +// (GHES 3.16 runners reject `using: node24` at job setup). +function escapeRegExp(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + function githubURLRegex(serverURL: string): RegExp { let regex = githubURLRegexCache.get(serverURL); if (!regex) { - const base = RegExp.escape(normalizeBaseUrl(serverURL)); + const base = escapeRegExp(normalizeBaseUrl(serverURL)); regex = new RegExp( `^${base}/([^/]+)/([^/]+)/(?:issues|pull)/(\\d+)/?(?:[?#].*)?$`, );