Compare commits

...

5 Commits

Author SHA1 Message Date
Claude Bot
7b513ad7ff Update claude-find-issues-for-pr.yml to v1 action
- Migrate from anthropics/claude-code-base-action@beta to anthropics/claude-code-action@v1
- Remove claude_env (v1 handles GitHub token natively)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 23:39:45 +00:00
robobun
37db234113 Add GitHub Action to find issues a PR might fix (#27991)
## Summary
- Adds a Claude-powered bot that runs when PRs are opened and searches
for open issues the PR may resolve
- Uses the same pattern as the existing dedupe issues bot: a workflow
triggers Claude Code with a `/find-issues` slash command
- The command reads the PR diff, launches 5 parallel search agents with
diverse keyword strategies, filters false positives, and comments on the
PR with up to 5 related open issues

## Test plan
- [ ] Open a test PR and verify the workflow triggers
- [ ] Verify the bot comments with relevant issues and doesn't duplicate
comments on re-runs
- [ ] Verify issues already referenced in the PR body are excluded from
results

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 23:39:45 +00:00
igorkofman
d19a267555 fix(process): check listener count before uninstalling signal handler (#27986)
## What does this PR do?

Fixes a bug where removing **one** of multiple signal listeners via
`process.off("SIGxxx", handler)` would unconditionally uninstall the OS
signal handler, breaking any remaining listeners for that signal.

### Root cause

`onDidChangeListener` fires on **every** listener add/remove (not just
first/last). In `onDidChangeListeners` in BunProcess.cpp, the
signal-removal path checked only whether the signal was registered in
`signalToContextIdsMap`, then unconditionally tore down the OS handler —
even if other JS listeners remained.

The IPC case a few lines above handles this correctly by checking
`totalListenerCount == 0`. This PR applies the same guard to the signal
path.

### Fix

Add `&& eventEmitter.listenerCount(eventName) == 0` to the removal
condition so the OS signal handler is only uninstalled when no listeners
remain.

### Test

`test/js/node/process/process-signal-listener-count.test.ts` verifies:
1. Removing one of multiple listeners keeps the handler installed (fails
on `main`, passes with fix)
2. Removing all listeners properly uninstalls (default signal behavior
kills the process)
3. Re-adding a listener after removing all reinstalls the handler

## How did you verify your code works?

- `USE_SYSTEM_BUN=1 bun test` → test 1 fails (confirms bug exists in
current release)
- `bun bd test` → all 3 tests pass
- Ran existing signal tests (`ctrl-c.test.ts`) — all pass

Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2026-03-10 23:39:45 +00:00
Dylan Conway
47279c24a9 Revert "fix(windows): use TerminateProcess to prevent NAPI module segfault on exit (#27829)"
This reverts commit b2e657ec51.
2026-03-10 23:39:45 +00:00
Claude Bot
0db2834b80 Update Claude Code GitHub Action to v1 with Opus 4.6
- Migrate from anthropics/claude-code-base-action@beta to anthropics/claude-code-action@v1
- Switch model from claude-sonnet-4-5 to claude-opus-4-6[1m]
- Remove claude_env (v1 handles GitHub token natively)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 23:36:08 +00:00
6 changed files with 223 additions and 24 deletions

View File

@@ -0,0 +1,50 @@
---
allowed-tools: Bash(gh pr view:*), Bash(gh pr diff:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh issue view:*), Bash(gh api:*), Bash(gh pr comment:*)
description: Find GitHub issues that a PR might fix
---
# Find issues for PR command
Find open GitHub issues that a pull request might fix. Include all likely matches — do not artificially limit the number of results.
To do this, follow these steps precisely:
1. Use an agent to check if the PR (a) is closed/merged, or (b) already has a related-issues comment (check for the exact HTML marker `<!-- find-issues-bot:marker -->` in the PR comments - ignore other bot comments). If so, do not proceed.
2. Use an agent to view the PR title, body, and diff (`gh pr view` and `gh pr diff`), and ask the agent to return a summary of:
- What the PR changes (files modified, functions changed, features added/fixed)
- Key technical terms, error messages, API names, or module names involved
- Any issue numbers already referenced in the PR body or commit messages
3. Then, launch 5 parallel agents to search GitHub for open issues that this PR might fix, using diverse keywords and search approaches derived from the summary in Step 2. **IMPORTANT**: Always scope searches with `repo:owner/repo` to constrain results to the current repository only. Each agent should try a different search strategy:
- Agent 1: Search using error messages or symptoms described in the diff
- Agent 2: Search using feature/module names from the changed files
- Agent 3: Search using API names or function names that were modified
- Agent 4: Search using keywords from the PR title and description
- Agent 5: Search using broader terms related to the area of code changed
4. Next, feed the results from Steps 2 and 3 into another agent, so that it can filter out false positives that are likely not actually related to the PR's changes. Exclude issues already referenced in the PR body (e.g. "fixes #123", "closes #456", "resolves #789"). Only keep issues where the PR changes are clearly relevant to the issue. If there are no related issues remaining, do not proceed.
5. Finally, comment on the PR with all related open issues found (or zero, if there are no likely matches). Do not cap the number — list every issue that is a likely match.
Notes (be sure to tell this to your agents, too):
- Use `gh` to interact with GitHub, rather than web fetch
- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.)
- Make a todo list first
- Always scope searches with `repo:owner/repo` to prevent cross-repo false positives
- Only match against **open** issues - do not suggest closed issues
- Exclude issues that are already linked in the PR description
- For your comment, follow the following format precisely (assuming for this example that you found 3 related issues):
---
Found 3 issues this PR may fix:
1. <link to issue> - <one-line summary of why this PR is relevant>
2. <link to issue> - <one-line summary of why this PR is relevant>
3. <link to issue> - <one-line summary of why this PR is relevant>
> If this is helpful, consider adding `Fixes #<number>` to the PR description to auto-close the issue on merge.
🤖 Generated with [Claude Code](https://claude.ai/code)
<!-- find-issues-bot:marker -->
---

View File

@@ -25,10 +25,8 @@ jobs:
uses: actions/checkout@v4
- name: Run Claude Code slash command
uses: anthropics/claude-code-base-action@beta
uses: anthropics/claude-code-action@v1
with:
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--model claude-sonnet-4-5-20250929"
claude_env: |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
claude_args: "--model claude-opus-4-6[1m]"

View File

@@ -0,0 +1,33 @@
name: Claude Find Issues for PR
on:
pull_request:
types: [opened]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to find related issues for'
required: true
type: string
jobs:
claude-find-issues:
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: claude-find-issues-${{ github.event.pull_request.number || inputs.pr_number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run Claude Code slash command
uses: anthropics/claude-code-action@v1
with:
prompt: "/find-issues ${{ github.repository }}/pull/${{ github.event.pull_request.number || inputs.pr_number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--model claude-opus-4-6[1m]"

View File

@@ -121,25 +121,7 @@ pub fn exit(code: u32) noreturn {
.mac => std.c.exit(@bitCast(code)),
.windows => {
Bun__onExit();
// Use TerminateProcess instead of ExitProcess to skip
// DLL_PROCESS_DETACH notifications. ExitProcess terminates all
// threads first, then calls DllMain(DLL_PROCESS_DETACH) for every
// loaded DLL. Native addons (NAPI modules such as skia-canvas)
// can crash during this phase because their worker threads have
// been killed and GC-dependent buffer finalizers haven't run.
// TerminateProcess skips DLL cleanup entirely, matching the
// behavior on Linux where quick_exit() also skips library
// teardown. Bun's own cleanup has already run via Bun__onExit().
const rc = std.os.windows.kernel32.TerminateProcess(
std.os.windows.kernel32.GetCurrentProcess(),
code,
);
// TerminateProcess should not return on the current process, but
// if it somehow fails, fall back to ExitProcess.
if (rc == 0) {
std.os.windows.kernel32.ExitProcess(code);
}
unreachable;
std.os.windows.kernel32.ExitProcess(code);
},
else => {
if (Environment.enable_asan) {

View File

@@ -1507,7 +1507,7 @@ static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& e
signalToContextIdsMap->set(signalNumber, signal_handle);
}
} else {
if (signalToContextIdsMap->find(signalNumber) != signalToContextIdsMap->end()) {
if (signalToContextIdsMap->find(signalNumber) != signalToContextIdsMap->end() && eventEmitter.listenerCount(eventName) == 0) {
#if !OS(WINDOWS)
if (void (*oldHandler)(int) = signal(signalNumber, SIG_DFL); oldHandler != forwardSignal) {

View File

@@ -0,0 +1,136 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, isWindows, normalizeBunSnapshot } from "harness";
// When multiple listeners are registered for the same signal, removing one
// listener must NOT uninstall the underlying OS signal handler while other
// listeners remain.
test.skipIf(isWindows)("removing one of multiple signal listeners keeps the handler installed", async () => {
const script = /*js*/ `
const { promise, resolve } = Promise.withResolvers();
let handlerBCount = 0;
function handlerA() {
console.log("handlerA fired (bug: I was removed!)");
}
function handlerB() {
handlerBCount++;
console.log("handlerB fired", handlerBCount);
if (handlerBCount === 2) {
resolve();
}
}
process.on("SIGUSR2", handlerA);
process.on("SIGUSR2", handlerB);
// Remove handlerA - handlerB should still receive signals.
process.off("SIGUSR2", handlerA);
// Send ourselves the signal twice.
process.kill(process.pid, "SIGUSR2");
// Wait for first signal, then send again.
await new Promise(r => setImmediate(r));
await new Promise(r => setImmediate(r));
process.kill(process.pid, "SIGUSR2");
await promise;
console.log("done");
`;
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", script],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(normalizeBunSnapshot(stdout)).toMatchInlineSnapshot(`
"handlerB fired 1
handlerB fired 2
done"
`);
expect(exitCode).toBe(0);
});
// Verify that removing ALL listeners does properly uninstall the handler,
// so the process dies with the default signal behavior.
test.skipIf(isWindows)("removing all signal listeners uninstalls the handler (default signal behavior)", async () => {
const script = /*js*/ `
function handlerA() {}
function handlerB() {}
process.on("SIGUSR2", handlerA);
process.on("SIGUSR2", handlerB);
process.off("SIGUSR2", handlerA);
process.off("SIGUSR2", handlerB);
// Keep event loop alive briefly so signal can be delivered
setTimeout(() => {
// If we get here, the signal handler was incorrectly still installed
// (or signal was ignored). Exit with a distinct code.
process.exit(42);
}, 1000);
process.kill(process.pid, "SIGUSR2");
`;
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", script],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("");
// Default SIGUSR2 behavior is to terminate the process with a signal.
// If the handler was correctly uninstalled, the process dies via signal (not exit code 42).
expect(exitCode).not.toBe(42);
expect(exitCode).not.toBe(0);
expect(proc.signalCode).not.toBeNull();
});
// Re-adding a listener after all were removed should reinstall the handler.
test.skipIf(isWindows)("re-adding a listener after removing all reinstalls the handler", async () => {
const script = /*js*/ `
const { promise, resolve } = Promise.withResolvers();
function handlerA() {}
function handlerB() {
console.log("handlerB fired");
resolve();
}
process.on("SIGUSR2", handlerA);
process.off("SIGUSR2", handlerA);
process.on("SIGUSR2", handlerB);
process.kill(process.pid, "SIGUSR2");
await promise;
console.log("done");
`;
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", script],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(normalizeBunSnapshot(stdout)).toMatchInlineSnapshot(`
"handlerB fired
done"
`);
expect(exitCode).toBe(0);
});