Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand Down
2 changes: 1 addition & 1 deletion action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,5 @@ outputs:
description: "Human-readable error message when the chat fails."

runs:
using: "node24"
using: "node20"
main: "dist/index.js"
5 changes: 4 additions & 1 deletion dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/comment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
11 changes: 9 additions & 2 deletions src/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -27,10 +27,17 @@ export const DEFAULT_GITHUB_SERVER_URL = "https://github.com";
// comment).
const githubURLRegexCache = new Map<string, RegExp>();

// 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+)/?(?:[?#].*)?$`,
);
Expand Down