Skip to content

Measure and log GitHub API rate limit around daily AIC guardrail#38256

Merged
pelikhan merged 5 commits into
mainfrom
copilot/measure-github-api-rate-limit
Jun 10, 2026
Merged

Measure and log GitHub API rate limit around daily AIC guardrail#38256
pelikhan merged 5 commits into
mainfrom
copilot/measure-github-api-rate-limit

Conversation

Copilot AI commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

The daily AI Credits guardrail inspects prior workflow runs via the GitHub Actions API and downloads usage artifacts, but there's no visibility into how much GitHub API rate limit this consumes. Without before/after snapshots, it's impossible to determine whether the guardrail itself is a significant rate-limit consumer.

Changes

github_rate_limit_logger.cjs

  • fetchAndLogRateLimit now returns {remaining, limit, used, reset} | null for the core resource, allowing callers to reuse a single rateLimit.get() call for both JSONL logging and in-memory tracking.

check_daily_aic_workflow_guardrail.cjs

  • Calls fetchAndLogRateLimit(githubClient, "daily-aic-guardrail-start") as the first API operation in main() — before getWorkflowRun — to write a baseline entry to github_rate_limits.jsonl.
  • After the full inspection loop (run listing + artifact downloads), calls fetchAndLogRateLimit(githubClient, "daily-aic-guardrail-end").
  • Immediately emits a structured consumption-delta log:
[daily-workflow-aic] GitHub API rate limit consumed by daily AIC guardrail: {
  "rateLimitBeforeInspection": 4850,
  "rateLimitAfterInspection": 4812,
  "consumed": 38,
  "limit": 5000,
  "reset": "2026-06-10T03:00:00.000Z"
}

The github_rate_limits.jsonl artifact entries for "daily-aic-guardrail-start" and "daily-aic-guardrail-end" (both with "source": "rate_limit_api") also capture the full per-resource breakdown for deeper analysis.

Tests

  • Updated fetchAndLogRateLimit return-value assertions (toBeUndefinedtoBeNull on error path); added three new tests covering success return, absent resources, and absent core resource.
  • New main() test verifies fetchAndLogRateLimit is called at least twice and the consumption-delta log entry is emitted with expected fields.

- fetchAndLogRateLimit now returns the core rate-limit snapshot
  ({remaining, limit, used, reset} or null) so callers can reuse the
  single API call for both logging and in-memory tracking
- check_daily_aic_workflow_guardrail.cjs calls fetchAndLogRateLimit
  with "daily-aic-guardrail-start" before the first API call to
  establish a before-guardrail baseline in the JSONL log
- After the artifact inspection loop, calls fetchAndLogRateLimit with
  "daily-aic-guardrail-end" and logs a consumption-delta entry
  (rateLimitBeforeInspection, rateLimitAfterInspection, consumed) via
  logDailyGuardrail so the rate limit hunger of the guardrail is
  observable in run artifacts
- Tests: updated toBeUndefined → toBeNull; added return-value tests
  for fetchAndLogRateLimit; added main() test verifying the start/end
  logs and consumption delta are emitted

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan marked this pull request as ready for review June 10, 2026 01:54
Copilot AI review requested due to automatic review settings June 10, 2026 01:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds explicit GitHub API rate-limit snapshots around the daily AI Credits (AIC) guardrail so operators can quantify how much rate-limit budget the guardrail consumes, and records those snapshots in the existing JSONL artifact for post-run analysis.

Changes:

  • Update fetchAndLogRateLimit() to return a core rate-limit snapshot ({remaining, limit, used, reset} | null) so callers can reuse the same API call for logging and in-memory tracking.
  • Add “start” and “end” rate-limit snapshots to the daily AIC guardrail flow and emit a structured delta log.
  • Expand unit tests to cover the new return shape and the daily guardrail delta logging behavior.
Show a summary per file
File Description
actions/setup/js/github_rate_limit_logger.cjs Make fetchAndLogRateLimit() return a core snapshot (or null) in addition to writing JSONL entries.
actions/setup/js/github_rate_limit_logger.test.cjs Update and add tests for the new fetchAndLogRateLimit() return contract and null cases.
actions/setup/js/check_daily_aic_workflow_guardrail.cjs Capture rate-limit snapshots at guardrail start/end and log a consumption delta.
actions/setup/js/check_daily_aic_workflow_guardrail.test.cjs Add a main() test asserting snapshot calls occur and a delta log is emitted.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

actions/setup/js/check_daily_aic_workflow_guardrail.cjs:325

  • The initial rate-limit snapshot is fetched but its return value is discarded, and rateLimit is still populated via a second rateLimit.get() call (getCoreRateLimitSnapshot). This defeats the new fetchAndLogRateLimit() API (single call for logging + in-memory tracking) and also means the later rateLimitBeforeInspection delta is not necessarily derived from the same snapshot that was logged as daily-aic-guardrail-start. Capture the returned snapshot and use it as the baseline (with a fallback to getCoreRateLimitSnapshot when null).
    // Capture a before-guardrail rate-limit snapshot and log it to the JSONL
    // so consumers can determine the baseline available quota before inspection starts.
    await fetchAndLogRateLimit(githubClient, "daily-aic-guardrail-start");
    const currentRun = await githubClient.rest.actions.getWorkflowRun({
      owner,
      repo,
      run_id: context.runId,
    });
    const rateLimit = await getCoreRateLimitSnapshot(githubClient);

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment on lines +470 to +477
const rateLimitEnd = await fetchAndLogRateLimit(githubClient, "daily-aic-guardrail-end");
logDailyGuardrail("GitHub API rate limit consumed by daily AIC guardrail", {
rateLimitBeforeInspection: rateLimit.remaining,
rateLimitAfterInspection: rateLimitEnd?.remaining ?? null,
consumed: rateLimit.remaining - (rateLimitEnd?.remaining ?? rateLimit.remaining),
limit: rateLimit.limit,
reset: rateLimit.reset,
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 2ac9616 to use the stored daily-aic-guardrail-start snapshot as the baseline when available, and to clamp consumed at 0 so resets cannot produce negative consumption.

Comment on lines +157 to +164
const coreData = resources.core;
if (!coreData || typeof coreData !== "object") return null;
return {
remaining: Number(coreData.remaining ?? 0),
limit: Number(coreData.limit ?? 0),
used: Number(coreData.used ?? 0),
reset: coreData.reset ? new Date(coreData.reset * 1000).toISOString() : "",
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 2ac9616: fetchAndLogRateLimit now returns null when required core fields are missing/non-numeric instead of coercing defaults, and tests now cover partial core payloads.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-reviewer skill

Copilot AI commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-reviewer skill

Ran the copilot-review skill (closest available equivalent to pr-reviewer) and reviewed the current PR feedback with it. No additional code changes are required based on the @copilot-mention thread.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer was skipped during the skills-based review.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel was skipped during test quality analysis.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #38256 does not have the 'implementation' label and has 0 new lines of code in business logic directories (≤100 threshold). Neither enforcement condition is met.

@github-actions github-actions Bot mentioned this pull request Jun 10, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — measurement is inaccurate and misleading from day one

The feature direction is right, but two blocking correctness bugs mean the logged consumed delta will always be wrong in normal operation, and will silently report 0 on any API failure.

Blocking issues (must fix)
  1. Line 318 — return value discarded, redundant API call, wrong baseline: fetchAndLogRateLimit was updated specifically so callers can skip getCoreRateLimitSnapshot — but the return value is thrown away and getCoreRateLimitSnapshot is still called. rateLimit.remaining (the before baseline) therefore reflects the state after the start snapshot + getWorkflowRun have already consumed quota, so consumed is systematically too low.

  2. Line 474 — consumed silently reports 0 on failure, and goes negative on reset-mid-run: When rateLimitEnd is null, the expression yields 0 — indistinguishable from genuine zero consumption. If the hourly window resets between snapshots, the value goes negative.

Non-blocking observations
  • Line 332 / 349 — early returns leave an orphaned daily-aic-guardrail-start JSONL entry with no matching end.
  • Test line 280>= 2 bound passes even without the end-snapshot call; should be >= 3 (or === 2 after removing the redundant snapshot call).
  • Test line 277 — assertions only check that key names appear in the serialized string; delta correctness is never validated.

🔎 Code quality review by PR Code Quality Reviewer · ⌖ 14 AIC

Comments that could not be inline-anchored

actions/setup/js/check_daily_aic_workflow_guardrail.cjs:318

Return value discarded — extra API call and wrong before baseline. fetchAndLogRateLimit now explicitly returns the core snapshot specifically so callers avoid a second rateLimit.get() call (see its docstring: "callers can use a single API call for both logging and in-memory rate-limit tracking"), but the return value is thrown away here and getCoreRateLimitSnapshot (line 324) issues an identical second call. This means rateLimit.remaining used as rateLimitBeforeInspection

actions/setup/js/check_daily_aic_workflow_guardrail.cjs:474

consumed silently lies when the end snapshot fails or when the rate-limit window resets mid-run. Two independent bugs in this expression:

  1. When rateLimitEnd is null (API error), the fallback is rateLimit.remaining, so consumed = 0. Zero looks like a successful measurement of no consumption — there is no signal distinguishing "guardrail used zero quota" from "we could not measure it".
  2. If the hourly rate-limit window resets between the before and after snapshots, `rate…
actions/setup/js/check_daily_aic_workflow_guardrail.cjs:332

Early return leaves the daily-aic-guardrail-start JSONL entry orphaned. fetchAndLogRateLimit(&quot;daily-aic-guardrail-start&quot;) was already called unconditionally, but this path exits before the matching &quot;daily-aic-guardrail-end&quot; entry or the consumption-delta log are ever emitted. The same problem applies to the maxInspectableRuns &lt;= 0 early return at line 349.

<details>
<summary>💡 Suggested fix</summary>

Move fetchAndLogRateLimit(&quot;daily-aic-guardrail-start&quot;) to just before the cand…

actions/setup/js/check_daily_aic_workflow_guardrail.test.cjs:280

&gt;= 2 lower bound cannot catch regression that removes the end-snapshot call. The actual call sequence is: fetchAndLogRateLimit-start (1) + getCoreRateLimitSnapshot (2) + fetchAndLogRateLimit-end (3) = minimum 3 calls. An assertion of &gt;= 2 passes even if the end measurement is deleted entirely, so the test provides no regression guard for the feature it claims to test.

<details>
<summary>💡 Suggested fix</summary>

// Must be at least 3: start snapshot + getCoreRateLimitSnap…

</details>

<details><summary>actions/setup/js/check_daily_aic_workflow_guardrail.test.cjs:277</summary>

**Assertions only verify key names exist in the serialized JSON string, never the computed values.** `toContain(&quot;consumed&quot;)` passes for `consumed: 0`, `consumed: null`, `consumed: -500`, or any other value — the delta arithmetic is entirely untested.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Parse the log line and assert the numeric properties directly:

```js
const detailsMatch = consumptionLog.match(/\[daily-workflow-aic\].*?: (\{.*\})$/);
const details = JSON.parse(detailsMatch[1]);
ex…

</details>

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Ran the PR finisher flow and addressed the in-scope review threads with fixes in 2ac9616 (plus test-case clarification in 38a2b10). Local validation passed (make fmt, make lint, make test-unit, make test, make recompile).

@pelikhan pelikhan merged commit cc24000 into main Jun 10, 2026
13 checks passed
@pelikhan pelikhan deleted the copilot/measure-github-api-rate-limit branch June 10, 2026 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants