Skip to content
59 changes: 56 additions & 3 deletions actions/setup/js/effective_tokens_context.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,51 @@ function parsePositiveIntegerString(value) {
return "";
}

/**
* Compare two integer strings using BigInt.
* Returns false when either value is missing or cannot be parsed as an integer.
*
* @param {string} left
* @param {string} right
* @returns {boolean}
*/
function isIntegerStringGreaterThanOrEqual(left, right) {
if (!left || !right) {
return false;
}

try {
return BigInt(left) >= BigInt(right);
} catch {
return false;
}
}

/**
* Decide whether an ET rate-limit signal should be surfaced as budget exhaustion.
* A missing signal always means "no". When the signal is present but one of the
* token counts is unavailable, keep reporting the condition; otherwise require the
* effective-token count to meet or exceed the configured max.
*
* @param {boolean} hasRateLimitSignal
* @param {string} effectiveTokens
* @param {string} maxEffectiveTokens
* @returns {boolean}
*/
function shouldReportEffectiveTokensRateLimitError(hasRateLimitSignal, effectiveTokens, maxEffectiveTokens) {
if (!hasRateLimitSignal) {
return false;
}

if (!effectiveTokens || !maxEffectiveTokens) {
// Conservative fallback: when a rate-limit signal exists but the numeric budget
// values are unavailable, keep surfacing the ET failure instead of suppressing it.
return true;
}

return isIntegerStringGreaterThanOrEqual(effectiveTokens, maxEffectiveTokens);
}

/**
* @param {unknown} value
* @returns {boolean}
Expand Down Expand Up @@ -223,10 +268,18 @@ function parseEffectiveTokensErrorInfoFromAuditLog(auditJsonlPathOverride) {
*/
function resolveEffectiveTokensFailureState() {
const parsedEffectiveTokensErrorInfo = parseEffectiveTokensErrorInfoFromAuditLog();
// Treat invalid env fallbacks as missing so they do not produce misleading ET math.
const envEffectiveTokens = parsePositiveIntegerString(process.env.GH_AW_EFFECTIVE_TOKENS);
const envMaxEffectiveTokens = parsePositiveIntegerString(process.env.GH_AW_MAX_EFFECTIVE_TOKENS);
const effectiveTokens = parsedEffectiveTokensErrorInfo.effectiveTokens || envEffectiveTokens || "";
const maxEffectiveTokens = parseMaxEffectiveTokensFromAuditLog() || envMaxEffectiveTokens || "";
const rawEffectiveTokensRateLimitError = parsedEffectiveTokensErrorInfo.rateLimitError || process.env.GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR === "true";
const effectiveTokensRateLimitError = shouldReportEffectiveTokensRateLimitError(rawEffectiveTokensRateLimitError, effectiveTokens, maxEffectiveTokens);

return {
effectiveTokens: parsedEffectiveTokensErrorInfo.effectiveTokens || process.env.GH_AW_EFFECTIVE_TOKENS || "",
maxEffectiveTokens: parseMaxEffectiveTokensFromAuditLog() || process.env.GH_AW_MAX_EFFECTIVE_TOKENS || "",
effectiveTokensRateLimitError: parsedEffectiveTokensErrorInfo.rateLimitError || process.env.GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR === "true",
effectiveTokens,
maxEffectiveTokens,
effectiveTokensRateLimitError,
};
}

Expand Down
103 changes: 103 additions & 0 deletions actions/setup/js/handle_agent_failure.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1879,6 +1879,109 @@ describe("handle_agent_failure", () => {
});
});

describe("resolveEffectiveTokensFailureState", () => {
const fs = require("fs");
const os = require("os");
const path = require("path");

let tmpDir;
let resolveEffectiveTokensFailureState;

beforeEach(() => {
vi.resetModules();
({ resolveEffectiveTokensFailureState } = require("./effective_tokens_context.cjs"));
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "aw-resolve-et-"));
});

afterEach(() => {
delete process.env.GH_AW_AGENT_OUTPUT;
delete process.env.GH_AW_EFFECTIVE_TOKENS;
delete process.env.GH_AW_MAX_EFFECTIVE_TOKENS;
delete process.env.GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR;
if (tmpDir && fs.existsSync(tmpDir)) {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

it("suppresses ET budget exhaustion when usage is below the configured maximum", () => {
const auditDir = path.join(tmpDir, "sandbox", "firewall", "audit");
fs.mkdirSync(auditDir, { recursive: true });
fs.writeFileSync(
path.join(auditDir, "log.jsonl"),
JSON.stringify({
_schema: "audit/v0.26.0",
ts: 1,
effective_tokens: 2097968,
max_effective_tokens: 10000000,
effective_tokens_rate_limit_error: true,
})
);
process.env.GH_AW_AGENT_OUTPUT = path.join(tmpDir, "agent_output.json");

expect(resolveEffectiveTokensFailureState()).toEqual({
effectiveTokens: "2097968",
maxEffectiveTokens: "10000000",
effectiveTokensRateLimitError: false,
});
});

it("keeps ET budget exhaustion when usage meets the configured maximum", () => {
const auditDir = path.join(tmpDir, "sandbox", "firewall", "audit");
fs.mkdirSync(auditDir, { recursive: true });
fs.writeFileSync(
path.join(auditDir, "log.jsonl"),
JSON.stringify({
_schema: "audit/v0.26.0",
ts: 1,
effective_tokens: 10000000,
max_effective_tokens: 10000000,
effective_tokens_rate_limit_error: true,
})
);
process.env.GH_AW_AGENT_OUTPUT = path.join(tmpDir, "agent_output.json");

expect(resolveEffectiveTokensFailureState()).toEqual({
effectiveTokens: "10000000",
maxEffectiveTokens: "10000000",
effectiveTokensRateLimitError: true,
});
});

it("keeps ET budget exhaustion when the rate-limit signal is present but no max is available", () => {
process.env.GH_AW_EFFECTIVE_TOKENS = "2097968";
process.env.GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR = "true";

expect(resolveEffectiveTokensFailureState()).toEqual({
Comment on lines +1950 to +1954
effectiveTokens: "2097968",
maxEffectiveTokens: "",
effectiveTokensRateLimitError: true,
});
});

it("ignores invalid env token counts when reconciling ET budget exhaustion", () => {
process.env.GH_AW_EFFECTIVE_TOKENS = "2097968";
process.env.GH_AW_MAX_EFFECTIVE_TOKENS = "not-a-number";
process.env.GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR = "true";

expect(resolveEffectiveTokensFailureState()).toEqual({
effectiveTokens: "2097968",
maxEffectiveTokens: "",
effectiveTokensRateLimitError: true,
});
});

it("does not report ET budget exhaustion without a rate-limit signal", () => {
process.env.GH_AW_EFFECTIVE_TOKENS = "10000000";
process.env.GH_AW_MAX_EFFECTIVE_TOKENS = "10000000";

expect(resolveEffectiveTokensFailureState()).toEqual({
effectiveTokens: "10000000",
maxEffectiveTokens: "10000000",
effectiveTokensRateLimitError: false,
});
});
});

describe("buildEffectiveTokensRateLimitErrorContext", () => {
let buildEffectiveTokensRateLimitErrorContext;

Expand Down