fix: replace domain-specific exit handler with generic signal handler registry (#299)
* fix: replace domain-specific exit handler with generic signal handler registry Rewrite exitHandler.ts as a generic, domain-agnostic exit signal module that exports onExitSignal(handler) returning a dispose function. - subprocess.ts now registers via onExitSignal instead of direct process.on(SIGINT/SIGTERM) calls - resolveTokens registers a signal handler that captures tokens by closure, fixing the race condition where the exit handler would read the wrong token after disposal - Remove setupExitHandler and runCleanup — domain cleanup is handled by post.ts + await using Co-authored-by: Cursor <cursoragent@cursor.com> * tweaks * simplify handler installation * extract to util * fix race in dispose * wrap dispose body in try/finally to ensure disposingRef always settles --------- Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
This commit is contained in:
committed by
pullfrog[bot]
parent
70f1c47a28
commit
ee100354da
@@ -148,11 +148,11 @@ var require_command = __commonJS({
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.issue = exports.issueCommand = void 0;
|
||||
var os = __importStar(__require("os"));
|
||||
var os2 = __importStar(__require("os"));
|
||||
var utils_1 = require_utils();
|
||||
function issueCommand(command, properties, message) {
|
||||
const cmd = new Command(command, properties, message);
|
||||
process.stdout.write(cmd.toString() + os.EOL);
|
||||
process.stdout.write(cmd.toString() + os2.EOL);
|
||||
}
|
||||
exports.issueCommand = issueCommand;
|
||||
function issue3(name, message = "") {
|
||||
@@ -236,7 +236,7 @@ var require_file_command = __commonJS({
|
||||
exports.prepareKeyValueMessage = exports.issueFileCommand = void 0;
|
||||
var crypto2 = __importStar(__require("crypto"));
|
||||
var fs3 = __importStar(__require("fs"));
|
||||
var os = __importStar(__require("os"));
|
||||
var os2 = __importStar(__require("os"));
|
||||
var utils_1 = require_utils();
|
||||
function issueFileCommand(command, message) {
|
||||
const filePath = process.env[`GITHUB_${command}`];
|
||||
@@ -246,7 +246,7 @@ var require_file_command = __commonJS({
|
||||
if (!fs3.existsSync(filePath)) {
|
||||
throw new Error(`Missing file at path: ${filePath}`);
|
||||
}
|
||||
fs3.appendFileSync(filePath, `${(0, utils_1.toCommandValue)(message)}${os.EOL}`, {
|
||||
fs3.appendFileSync(filePath, `${(0, utils_1.toCommandValue)(message)}${os2.EOL}`, {
|
||||
encoding: "utf8"
|
||||
});
|
||||
}
|
||||
@@ -260,7 +260,7 @@ var require_file_command = __commonJS({
|
||||
if (convertedValue.includes(delimiter)) {
|
||||
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
|
||||
}
|
||||
return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`;
|
||||
return `${key}<<${delimiter}${os2.EOL}${convertedValue}${os2.EOL}${delimiter}`;
|
||||
}
|
||||
exports.prepareKeyValueMessage = prepareKeyValueMessage;
|
||||
}
|
||||
@@ -11280,7 +11280,7 @@ var require_RetryHandler = __commonJS({
|
||||
return diff;
|
||||
}
|
||||
var RetryHandler = class _RetryHandler {
|
||||
constructor(opts, handlers) {
|
||||
constructor(opts, handlers2) {
|
||||
const { retryOptions, ...dispatchOpts } = opts;
|
||||
const {
|
||||
// Retry scoped
|
||||
@@ -11295,8 +11295,8 @@ var require_RetryHandler = __commonJS({
|
||||
retryAfter,
|
||||
statusCodes
|
||||
} = retryOptions ?? {};
|
||||
this.dispatch = handlers.dispatch;
|
||||
this.handler = handlers.handler;
|
||||
this.dispatch = handlers2.dispatch;
|
||||
this.handler = handlers2.handler;
|
||||
this.opts = dispatchOpts;
|
||||
this.abort = null;
|
||||
this.aborted = false;
|
||||
@@ -17513,7 +17513,7 @@ var require_lib = __commonJS({
|
||||
}
|
||||
exports.isHttps = isHttps;
|
||||
var HttpClient = class {
|
||||
constructor(userAgent2, handlers, requestOptions) {
|
||||
constructor(userAgent2, handlers2, requestOptions) {
|
||||
this._ignoreSslError = false;
|
||||
this._allowRedirects = true;
|
||||
this._allowRedirectDowngrade = false;
|
||||
@@ -17523,7 +17523,7 @@ var require_lib = __commonJS({
|
||||
this._keepAlive = false;
|
||||
this._disposed = false;
|
||||
this.userAgent = userAgent2;
|
||||
this.handlers = handlers || [];
|
||||
this.handlers = handlers2 || [];
|
||||
this.requestOptions = requestOptions;
|
||||
if (requestOptions) {
|
||||
if (requestOptions.ignoreSslError != null) {
|
||||
@@ -18983,7 +18983,7 @@ var require_toolrunner = __commonJS({
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.argStringToArray = exports.ToolRunner = void 0;
|
||||
var os = __importStar(__require("os"));
|
||||
var os2 = __importStar(__require("os"));
|
||||
var events = __importStar(__require("events"));
|
||||
var child = __importStar(__require("child_process"));
|
||||
var path3 = __importStar(__require("path"));
|
||||
@@ -19038,12 +19038,12 @@ var require_toolrunner = __commonJS({
|
||||
_processLineBuffer(data, strBuffer, onLine) {
|
||||
try {
|
||||
let s = strBuffer + data.toString();
|
||||
let n = s.indexOf(os.EOL);
|
||||
let n = s.indexOf(os2.EOL);
|
||||
while (n > -1) {
|
||||
const line = s.substring(0, n);
|
||||
onLine(line);
|
||||
s = s.substring(n + os.EOL.length);
|
||||
n = s.indexOf(os.EOL);
|
||||
s = s.substring(n + os2.EOL.length);
|
||||
n = s.indexOf(os2.EOL);
|
||||
}
|
||||
return s;
|
||||
} catch (err) {
|
||||
@@ -19212,7 +19212,7 @@ var require_toolrunner = __commonJS({
|
||||
}
|
||||
const optionsNonNull = this._cloneExecOptions(this.options);
|
||||
if (!optionsNonNull.silent && optionsNonNull.outStream) {
|
||||
optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL);
|
||||
optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os2.EOL);
|
||||
}
|
||||
const state = new ExecState(optionsNonNull, this.toolPath);
|
||||
state.on("debug", (message) => {
|
||||
@@ -19700,7 +19700,7 @@ var require_core = __commonJS({
|
||||
var command_1 = require_command();
|
||||
var file_command_1 = require_file_command();
|
||||
var utils_1 = require_utils();
|
||||
var os = __importStar(__require("os"));
|
||||
var os2 = __importStar(__require("os"));
|
||||
var path3 = __importStar(__require("path"));
|
||||
var oidc_utils_1 = require_oidc_utils();
|
||||
var ExitCode;
|
||||
@@ -19768,7 +19768,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
||||
if (filePath) {
|
||||
return (0, file_command_1.issueFileCommand)("OUTPUT", (0, file_command_1.prepareKeyValueMessage)(name, value2));
|
||||
}
|
||||
process.stdout.write(os.EOL);
|
||||
process.stdout.write(os2.EOL);
|
||||
(0, command_1.issueCommand)("set-output", { name }, (0, utils_1.toCommandValue)(value2));
|
||||
}
|
||||
exports.setOutput = setOutput2;
|
||||
@@ -19802,7 +19802,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
||||
}
|
||||
exports.notice = notice;
|
||||
function info2(message) {
|
||||
process.stdout.write(message + os.EOL);
|
||||
process.stdout.write(message + os2.EOL);
|
||||
}
|
||||
exports.info = info2;
|
||||
function startGroup3(name) {
|
||||
@@ -135779,6 +135779,31 @@ import { join } from "node:path";
|
||||
var core3 = __toESM(require_core(), 1);
|
||||
import assert2 from "node:assert/strict";
|
||||
|
||||
// utils/exitHandler.ts
|
||||
import os from "node:os";
|
||||
var handlers = /* @__PURE__ */ new Set();
|
||||
var installed = false;
|
||||
function onExitSignal(handler2) {
|
||||
installSignalHandlers();
|
||||
handlers.add(handler2);
|
||||
return () => {
|
||||
handlers.delete(handler2);
|
||||
};
|
||||
}
|
||||
function installSignalHandlers() {
|
||||
if (installed) return;
|
||||
installed = true;
|
||||
async function handleSignal(signal) {
|
||||
await Promise.allSettled([...handlers].map((h) => Promise.try(h, signal)));
|
||||
exitWithSignal(signal);
|
||||
}
|
||||
process.on("SIGINT", handleSignal);
|
||||
process.on("SIGTERM", handleSignal);
|
||||
}
|
||||
function exitWithSignal(signal) {
|
||||
process.exit(128 + os.constants.signals[signal]);
|
||||
}
|
||||
|
||||
// utils/github.ts
|
||||
var core2 = __toESM(require_core(), 1);
|
||||
import { createSign } from "node:crypto";
|
||||
@@ -139728,18 +139753,31 @@ async function resolveTokens(params) {
|
||||
log.info("\xBB acquired full MCP token");
|
||||
const revertGithubToken = setEnvironmentVariable("GITHUB_TOKEN", mcpToken);
|
||||
mcpTokenValue = mcpToken;
|
||||
return {
|
||||
gitToken,
|
||||
mcpToken,
|
||||
async [Symbol.asyncDispose]() {
|
||||
let disposingRef;
|
||||
const dispose = async () => {
|
||||
if (disposingRef) {
|
||||
return disposingRef.promise;
|
||||
}
|
||||
disposingRef = Promise.withResolvers();
|
||||
try {
|
||||
mcpTokenValue = void 0;
|
||||
revertGithubToken();
|
||||
await Promise.all([
|
||||
revokeGitHubInstallationToken(gitToken),
|
||||
revokeGitHubInstallationToken(mcpToken)
|
||||
]);
|
||||
} finally {
|
||||
removeSignalHandler();
|
||||
disposingRef.resolve();
|
||||
disposingRef = void 0;
|
||||
}
|
||||
};
|
||||
const removeSignalHandler = onExitSignal(dispose);
|
||||
return {
|
||||
gitToken,
|
||||
mcpToken,
|
||||
[Symbol.asyncDispose]: dispose
|
||||
};
|
||||
}
|
||||
function getGitHubInstallationToken() {
|
||||
assert2(mcpTokenValue, "tokens not set. call resolveTokens first.");
|
||||
@@ -140647,13 +140685,13 @@ function createProcessOutputActivityTimeout(ctx) {
|
||||
var activeChildren = /* @__PURE__ */ new Map();
|
||||
var externalSignalHandler = null;
|
||||
function trackChild(options) {
|
||||
installSignalHandler();
|
||||
activeChildren.set(options.child, options.killGroup ?? false);
|
||||
}
|
||||
function untrackChild(child) {
|
||||
activeChildren.delete(child);
|
||||
}
|
||||
function killTrackedChildren() {
|
||||
const count = activeChildren.size;
|
||||
for (const entry of activeChildren) {
|
||||
const child = entry[0];
|
||||
const killGroup = entry[1];
|
||||
@@ -140666,34 +140704,27 @@ function killTrackedChildren() {
|
||||
}
|
||||
child.kill("SIGKILL");
|
||||
}
|
||||
return count;
|
||||
}
|
||||
function cleanupAndExit(signal) {
|
||||
const count = killTrackedChildren();
|
||||
if (count > 0) {
|
||||
log.info(`\xBB received ${signal}, killing ${count} subprocess(es)...`);
|
||||
}
|
||||
setTimeout(() => process.exit(1), 500).unref();
|
||||
process.exit(1);
|
||||
}
|
||||
function handleSignal(signal) {
|
||||
if (externalSignalHandler) {
|
||||
externalSignalHandler(signal);
|
||||
return;
|
||||
}
|
||||
cleanupAndExit(signal);
|
||||
}
|
||||
var handlersInstalled = false;
|
||||
function installSignalHandlers() {
|
||||
function installSignalHandler() {
|
||||
if (handlersInstalled) return;
|
||||
handlersInstalled = true;
|
||||
process.on("SIGINT", () => handleSignal("SIGINT"));
|
||||
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
||||
onExitSignal((signal) => {
|
||||
if (externalSignalHandler) {
|
||||
externalSignalHandler(signal);
|
||||
return;
|
||||
}
|
||||
const count = activeChildren.size;
|
||||
if (count > 0) {
|
||||
log.info(`\xBB received ${signal}, killing ${count} subprocess(es)...`);
|
||||
}
|
||||
killTrackedChildren();
|
||||
});
|
||||
}
|
||||
async function spawn2(options) {
|
||||
const { cmd, args: args2, env: env2, input, timeout, cwd, stdio, onStdout, onStderr } = options;
|
||||
const activityTimeoutMs = options.activityTimeout ?? DEFAULT_ACTIVITY_TIMEOUT_MS;
|
||||
installSignalHandlers();
|
||||
installSignalHandler();
|
||||
const startTime = performance3.now();
|
||||
let stdoutBuffer = "";
|
||||
let stderrBuffer = "";
|
||||
@@ -141302,7 +141333,6 @@ function stripExistingFooter(body) {
|
||||
}
|
||||
|
||||
// mcp/comment.ts
|
||||
var LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
|
||||
async function buildCommentFooter({
|
||||
agent: agent2,
|
||||
octokit,
|
||||
@@ -145378,10 +145408,10 @@ var cursorEffortModels = {
|
||||
max: "opus-4.5-thinking"
|
||||
};
|
||||
async function installCursor() {
|
||||
const os = process.platform === "darwin" ? "darwin" : "linux";
|
||||
const os2 = process.platform === "darwin" ? "darwin" : "linux";
|
||||
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
||||
return await installFromDirectTarball({
|
||||
url: `https://downloads.cursor.com/lab/${CURSOR_CLI_VERSION}/${os}/${arch}/agent-cli-package.tar.gz`,
|
||||
url: `https://downloads.cursor.com/lab/${CURSOR_CLI_VERSION}/${os2}/${arch}/agent-cli-package.tar.gz`,
|
||||
executablePath: "cursor-agent",
|
||||
stripComponents: 1
|
||||
});
|
||||
@@ -146495,83 +146525,6 @@ ${ctx.error}` : ctx.error;
|
||||
ctx.toolState.wasUpdated = true;
|
||||
}
|
||||
|
||||
// utils/exitHandler.ts
|
||||
function buildErrorCommentBody(params) {
|
||||
const workflowRunLink = params.runId ? `[workflow run logs](https://github.com/${params.owner}/${params.repo}/actions/runs/${params.runId})` : "workflow run logs";
|
||||
const errorMessage = params.isCancellation ? `This run was cancelled \u{1F6D1}
|
||||
|
||||
The workflow was cancelled before completion. Please check the ${workflowRunLink} for details.` : `This run croaked \u{1F635}
|
||||
|
||||
The workflow encountered an error before any progress could be reported. Please check the ${workflowRunLink} for details.`;
|
||||
const footer = buildPullfrogFooter({
|
||||
triggeredBy: true,
|
||||
workflowRun: params.runId ? { owner: params.owner, repo: params.repo, runId: params.runId } : void 0
|
||||
});
|
||||
return `${errorMessage}${footer}`;
|
||||
}
|
||||
var cleanupFn;
|
||||
function setupExitHandler(toolState) {
|
||||
let hasCleanedUp = false;
|
||||
async function cleanup(isCancellation) {
|
||||
if (hasCleanedUp) {
|
||||
return;
|
||||
}
|
||||
hasCleanedUp = true;
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const commentId = toolState.progressCommentId;
|
||||
const wasUpdated = toolState.wasUpdated === true;
|
||||
if (token && commentId && !wasUpdated) {
|
||||
try {
|
||||
const repoContext = parseRepoContext();
|
||||
const octokit = createOctokit(token);
|
||||
const existingComment = await octokit.rest.issues.getComment({
|
||||
owner: repoContext.owner,
|
||||
repo: repoContext.name,
|
||||
comment_id: commentId
|
||||
});
|
||||
const commentBody = existingComment.data.body || "";
|
||||
if (commentBody.startsWith(LEAPING_INTO_ACTION_PREFIX)) {
|
||||
const runId = process.env.GITHUB_RUN_ID;
|
||||
const body = buildErrorCommentBody({
|
||||
owner: repoContext.owner,
|
||||
repo: repoContext.name,
|
||||
runId,
|
||||
isCancellation
|
||||
});
|
||||
await octokit.rest.issues.updateComment({
|
||||
owner: repoContext.owner,
|
||||
repo: repoContext.name,
|
||||
comment_id: commentId,
|
||||
body
|
||||
});
|
||||
log.info("\xBB updated progress comment with error message");
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
if (token) {
|
||||
try {
|
||||
await revokeGitHubInstallationToken(token);
|
||||
log.debug("\xBB installation token revoked");
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanupFn = cleanup;
|
||||
function handleSignal2() {
|
||||
log.info("\xBB workflow cancelled, cleaning up...");
|
||||
cleanup(true).finally(() => process.exit(1));
|
||||
}
|
||||
process.on("SIGINT", handleSignal2);
|
||||
process.on("SIGTERM", handleSignal2);
|
||||
}
|
||||
async function runCleanup() {
|
||||
try {
|
||||
await cleanupFn?.(false);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
// utils/normalizeEnv.ts
|
||||
function maskValue(value2) {
|
||||
if (value2 && typeof value2 === "string" && value2.trim().length > 0) {
|
||||
@@ -146981,7 +146934,6 @@ async function main() {
|
||||
const toolState = initToolState({
|
||||
progressCommentId: typeof resolvedPromptInput !== "string" ? resolvedPromptInput.progressCommentId : void 0
|
||||
});
|
||||
setupExitHandler(toolState);
|
||||
resolveGit();
|
||||
const jobToken = getJobToken();
|
||||
const initialOctokit = createOctokit(jobToken);
|
||||
@@ -147141,9 +147093,7 @@ ${instructions.user}` : null,
|
||||
error: errorMessage
|
||||
};
|
||||
} finally {
|
||||
if (activityTimeout) {
|
||||
activityTimeout.stop();
|
||||
}
|
||||
activityTimeout?.stop();
|
||||
}
|
||||
} catch (_2) {
|
||||
var _error2 = _2, _hasError2 = true;
|
||||
@@ -147166,8 +147116,6 @@ async function run() {
|
||||
} catch (error49) {
|
||||
const errorMessage = error49 instanceof Error ? error49.message : "Unknown error occurred";
|
||||
core5.setFailed(`Action failed: ${errorMessage}`);
|
||||
} finally {
|
||||
await runCleanup();
|
||||
}
|
||||
}
|
||||
await run();
|
||||
|
||||
Reference in New Issue
Block a user