Skip to content

fix: job token budget, iteration cap → Failed, web cancel stops worker - #788

Merged
ilblackdragon merged 4 commits into
stagingfrom
fix/698-job-budget-iteration-cap-cancel
Mar 10, 2026
Merged

ilblackdragon merged 4 commits into
stagingfrom
fix/698-job-budget-iteration-cap-cancel

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

Summary

Fixes #698. Jobs could enter infinite retry loops burning millions of tokens because:

  • No token budget enforcement — JobContext::add_tokens() existed but was never called. Added configurable max_tokens_per_job (settings.json agent.max_tokens_per_job / AGENT_MAX_TOKENS_PER_JOB env var, default 0 = unlimited). Token usage is tracked after respond_with_tools() and the job fails on budget exceeded. Per-job override via metadata["max_tokens"].
  • Iteration cap → Stuck → self-repair loop — Changed mark_stuck() to mark_failed() for iteration cap and persistent rate limiting, so DefaultSelfRepair cannot restart them.
  • Web cancel didn't stop the worker — Cancel handler now calls scheduler.stop(job_id) which updates in-memory ContextManager AND aborts the worker task. Falls back to DB-only update when scheduler is unavailable.

Test plan

  • test_token_budget_exceeded_fails_job — token budget enforcement transitions to Failed
  • test_iteration_cap_marks_failed_not_stuck — iteration cap transitions to Failed, not Stuck
  • cargo clippy --all --all-features — zero warnings
  • cargo test — 2758 passed, 0 failed
  • Manual: set agent.max_tokens_per_job in settings.json, run a job, verify it stops at the budget
  • Manual: cancel a running agent job from the web UI, verify the worker actually stops

🤖 Generated with Claude Code

Copilot Bot review requested due to automatic review settings March 9, 2026 23:49
@github-actions github-actions Bot added scope: agent Agent core (agent loop, router, scheduler) scope: channel/web Web gateway channel scope: config Configuration labels Mar 9, 2026
@gemini-code-assist

ghost commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the stability and resource management of agent jobs. It introduces a token budget system to control LLM costs, refines job failure states to prevent undesirable retry loops, and improves the reliability of job cancellation from the web interface by ensuring worker tasks are properly terminated. These changes collectively contribute to more predictable and efficient agent operations.

Highlights

  • Token Budget Enforcement: Introduced a configurable max_tokens_per_job setting to prevent jobs from consuming excessive LLM tokens, with an option for per-job overrides via metadata. Jobs exceeding this budget will now fail.
  • Improved Job State Transitions: Modified the iteration cap and persistent rate limiting logic to transition jobs directly to a Failed state instead of Stuck, preventing agents from entering infinite self-repair loops.
  • Enhanced Web Cancellation: Implemented a more robust job cancellation mechanism from the web UI, which now attempts to stop the associated worker task via the scheduler, ensuring immediate termination of the agent's activity.
Changelog
  • .env.example
    • Added an example environment variable for AGENT_MAX_TOKENS_PER_JOB.
  • src/agent/dispatcher.rs
    • Updated test configurations to include the new max_tokens_per_job setting.
  • src/agent/scheduler.rs
    • Implemented logic to apply max_tokens_per_job from configuration or metadata when creating a new job.
    • Added a separate update to set the token budget in the job context.
  • src/agent/worker.rs
    • Changed job state transition from Stuck to Failed when max_iterations are exceeded.
    • Changed job state transition from Stuck to Failed for persistent LLM rate limiting.
    • Integrated token usage tracking after respond_with_tools() and enforced the max_tokens budget, failing the job if exceeded.
    • Added new test cases: test_token_budget_exceeded_fails_job and test_iteration_cap_marks_failed_not_stuck.
  • src/channels/web/handlers/jobs.rs
    • Modified jobs_cancel_handler to attempt stopping the worker via the scheduler first, then falling back to a DB-only status update if the scheduler is unavailable.
  • src/config/agent.rs
    • Added max_tokens_per_job field to AgentConfig struct.
    • Initialized max_tokens_per_job to 0 in AgentConfig::default().
    • Implemented parsing for AGENT_MAX_TOKENS_PER_JOB from environment variables and settings.
  • src/settings.rs
    • Added max_tokens_per_job field to AgentSettings struct.
    • Set default value of max_tokens_per_job to 0 in AgentSettings::default().
Activity
  • Verified test_token_budget_exceeded_fails_job to confirm token budget enforcement leads to job failure.
  • Verified test_iteration_cap_marks_failed_not_stuck to confirm iteration cap leads to job failure, not stuck state.
  • Ran cargo clippy --all --all-features with zero warnings.
  • Ran cargo test with 2758 tests passed and 0 failed.
  • Planned manual testing for agent.max_tokens_per_job and web cancellation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@github-actions github-actions Bot added size: M 50-199 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Mar 9, 2026

ghost 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 addresses runaway agent jobs by introducing per-job token budgets, changing iteration-cap/rate-limit terminal behavior to Failed, and making web “cancel” actually stop the running worker via the scheduler.

Changes:

  • Add configurable agent.max_tokens_per_job (settings + env) and plumb it into job creation with optional per-job metadata override.
  • Change iteration-cap and persistent rate-limit terminal state from Stuck to Failed in the worker loop.
  • Update web cancel handler to stop the worker via scheduler.stop(job_id) (with DB-only fallback).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/settings.rs Adds AgentSettings.max_tokens_per_job with default 0 (unlimited).
src/config/agent.rs Resolves max_tokens_per_job from env/settings into AgentConfig.
src/agent/scheduler.rs Applies token budget to new jobs, allowing metadata["max_tokens"] override.
src/agent/worker.rs Marks iteration/rate-limit terminal conditions as Failed; adds token usage tracking; adds tests.
src/channels/web/handlers/jobs.rs Uses scheduler stop on cancel to abort worker; falls back to DB update.
src/agent/dispatcher.rs Updates tests to include new AgentConfig field.
.env.example Documents AGENT_MAX_TOKENS_PER_JOB.

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

Comment thread src/agent/worker.rs
Comment on lines +489 to +500
// Track token usage from LLM call against the job budget.
let total_tokens = respond_output.usage.total() as u64;
if total_tokens > 0 {
let budget_result = self
.context_manager()
.update_context(self.job_id, |ctx| ctx.add_tokens(total_tokens))
.await?;
if let Err(msg) = budget_result {
self.mark_failed(&msg).await?;
return Ok(());
}
}

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

Token budget enforcement is only applied to the respond_with_tools() call when selections.is_empty(). Iterations that go through select_tools() (and any other LLM calls like planning) are not counted, so a job can still exceed the intended budget without being failed. Consider plumbing TokenUsage out of select_tools() / plan() (or adding a wrapper that records usage for every LLM call in the worker loop) so all LLM token consumption contributes to ctx.add_tokens(...).

Copilot uses AI. Check for mistakes.

ghost Mar 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Valid point. select_tools() makes an LLM call but does not expose TokenUsage in its return type. Plumbing usage out requires changing the Reasoning trait — a broader refactor. Added a NOTE comment acknowledging the gap in 7364930. Since there was zero token tracking before this PR, tracking respond_with_tools() alone is a meaningful improvement, and the iteration cap now also serves as a hard stop.

Comment thread src/agent/worker.rs
Comment on lines +1782 to +1828
#[tokio::test]
async fn test_token_budget_exceeded_fails_job() {
let worker = make_worker(vec![]).await;

// Transition to InProgress (required for mark_failed)
worker
.context_manager()
.update_context(worker.job_id, |ctx| {
ctx.transition_to(JobState::InProgress, None)
})
.await
.unwrap()
.unwrap();

// Set a token budget
worker
.context_manager()
.update_context(worker.job_id, |ctx| {
ctx.max_tokens = 100;
})
.await
.unwrap();

// Simulate adding tokens that exceed the budget
let budget_result = worker
.context_manager()
.update_context(worker.job_id, |ctx| ctx.add_tokens(200))
.await
.unwrap();

assert!(
budget_result.is_err(),
"Should return error when token budget exceeded"
);

// Verify that mark_failed transitions job to Failed
worker
.mark_failed(&budget_result.unwrap_err())
.await
.unwrap();
let ctx = worker
.context_manager()
.get_context(worker.job_id)
.await
.unwrap();
assert_eq!(ctx.state, JobState::Failed);
}

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

These new tests validate JobContext::add_tokens() and Worker::mark_failed(), but they don't exercise the newly added runtime path that increments tokens based on respond_with_tools().usage inside Worker::run(). Adding a test that runs a minimal worker loop iteration with a stub LLM returning a non-zero usage would better cover the regression this PR is addressing (budget enforcement from actual LLM calls).

Copilot uses AI. Check for mistakes.

ghost Mar 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged. Testing the full worker loop with a mock LLM that returns non-zero usage is substantial (requires mocking Reasoning, select_tools, respond_with_tools). The current tests cover the core path: add_tokens correctly returns an error on budget exceeded, and mark_failed correctly transitions the job. The integration between these pieces in the loop is straightforward (3 lines of glue code). A full integration test with a mock LLM would be valuable but is better suited as a follow-up.

Comment thread src/channels/web/handlers/jobs.rs Outdated
Comment on lines +286 to +307
// Try to stop via scheduler first (stops the actual worker).
let stopped_via_scheduler = if let Some(ref slot) = state.scheduler {
let guard = slot.read().await;
if let Some(ref scheduler) = *guard {
scheduler.stop(job_id).await.is_ok()
} else {
false
}
} else {
false
};

// If scheduler wasn't available, fall back to DB-only update.
if !stopped_via_scheduler {
store
.update_job_status(
job_id,
crate::context::JobState::Cancelled,
Some("Cancelled by user"),
)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

stopped_via_scheduler is computed with scheduler.stop(job_id).await.is_ok(), but Scheduler::stop() currently returns Ok(()) even when the job isn't in the in-memory scheduler map. That means this handler can skip the DB status update while the job remains active in the DB (and no worker was actually stopped). Fix by checking scheduler.is_running(job_id).await (or changing stop() to return NotFound) and only skipping the DB update when a running job was actually stopped (and/or always persist the Cancelled state).

Copilot uses AI. Check for mistakes.

ghost Mar 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch. Fixed in 7364930 — the cancel handler now always persists Cancelled to the DB, using scheduler.stop() as an additional best-effort step (to abort the worker task) rather than a replacement for the DB update. This handles the edge case where stop() returns Ok(()) for jobs not in the scheduler map.

ghost 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.

Code Review

This pull request introduces several important fixes to job lifecycle management, addressing potential infinite loops and token over-consumption. The changes to enforce a token budget, fail jobs on iteration caps, and ensure web cancellations properly stop worker tasks are well-implemented and crucial for stability. The new tests adequately cover the new failure conditions. I've kept the original suggestions to improve the conciseness and readability of the new logic, as they do not contradict any established rules.

Note: Security Review did not run due to the size of the PR.

Comment thread src/agent/worker.rs Outdated
Comment on lines +492 to +499
let budget_result = self
.context_manager()
.update_context(self.job_id, |ctx| ctx.add_tokens(total_tokens))
.await?;
if let Err(msg) = budget_result {
self.mark_failed(&msg).await?;
return Ok(());
}

ghost Mar 9, 2026

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.

medium

This logic for checking the token budget is correct, but it can be made more concise by combining the await? and the if let into a single statement. This improves readability by making the error handling path more direct.

Suggested change
let budget_result = self
.context_manager()
.update_context(self.job_id, |ctx| ctx.add_tokens(total_tokens))
.await?;
if let Err(msg) = budget_result {
self.mark_failed(&msg).await?;
return Ok(());
}
if let Err(msg) = self
.context_manager()
.update_context(self.job_id, |ctx| ctx.add_tokens(total_tokens))
.await?
{
self.mark_failed(&msg).await?;
return Ok(());
}

ghost Mar 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Applied — collapsed the nested if into a let-chain per clippy. Fixed in 7364930.

Comment thread src/channels/web/handlers/jobs.rs Outdated
Comment on lines +287 to +296
let stopped_via_scheduler = if let Some(ref slot) = state.scheduler {
let guard = slot.read().await;
if let Some(ref scheduler) = *guard {
scheduler.stop(job_id).await.is_ok()
} else {
false
}
} else {
false
};

ghost Mar 9, 2026

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.

medium

This logic to determine if the job was stopped via the scheduler is a bit nested. You can improve readability and reduce nesting by using an async block to scope the awaits and return the result directly.

let stopped_via_scheduler = async {
    if let Some(scheduler_slot) = &state.scheduler {
        if let Some(scheduler) = scheduler_slot.read().await.as_ref() {
            return scheduler.stop(job_id).await.is_ok();
        }
    }
    false
}.await;

ghost Mar 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Applied a similar simplification using let-chains (collapsed nested ifs). Fixed in 7364930.

ghost left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review

Three changes bundled here: token budget enforcement, iteration cap -> Failed (not Stuck), and web cancel stops worker via scheduler. All three are solid improvements.

Token Budget

  • ctx.add_tokens() / ctx.max_tokens pattern is clean. The metadata override (max_tokens in job metadata) is a nice touch for per-job control.
  • Concern: add_tokens returns Result via update_context, which returns Result<T, _> where T is the closure's return value. The double-unwrap pattern (await.unwrap() then check is_err()) in the test is fine, but in the worker loop the pattern is:
    let budget_result = self.context_manager().update_context(...).await?;
    if let Err(msg) = budget_result { ... }
    This is correct but would be clearer if add_tokens returned a named error type instead of Result<(), String>.

Iteration Cap -> Failed

  • Good fix. mark_stuck triggers self-repair which retries, creating an infinite loop. mark_failed is the correct terminal state for "tried too many times."

Web Cancel via Scheduler

  • The stopped_via_scheduler pattern correctly tries the in-memory scheduler first (which aborts the task handle), falling back to DB-only update.
  • Edge case: If scheduler.stop() succeeds (returns Ok) but the worker hasn't persisted its final state yet, the response says "cancelled" but the DB might still show InProgress briefly. Acceptable for now but worth a comment.

Tests

Both new tests (test_token_budget_exceeded_fails_job, test_iteration_cap_marks_failed_not_stuck) are good regression tests.

Overall looks good -- approve once the existing CI passes.

@ilblackdragon

ghost commented Mar 10, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review @zmanian!

Re: add_tokens returning Result<(), String> — agreed a named error type would be cleaner. That is pre-existing API surface in context/state.rs; changing it is a follow-up refactor.

Re: DB consistency edge case on cancel — fixed in 7364930. The handler now always persists Cancelled to the DB, using scheduler.stop() as best-effort to abort the worker. The fire-and-forget persist inside scheduler.stop() is a bonus but the handler no longer relies on it as the sole DB update.

Copilot Bot review requested due to automatic review settings March 10, 2026 01:54
@ilblackdragon
ilblackdragon force-pushed the fix/698-job-budget-iteration-cap-cancel branch from 7364930 to f7cfbeb Compare March 10, 2026 01:54
@github-actions github-actions Bot added scope: ci CI/CD workflows size: L 200-499 changed lines and removed size: M 50-199 changed lines labels Mar 10, 2026

ghost 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

.github/workflows/staging-ci.yml:122

  • The actions/create-github-app-token@v2 step now runs unconditionally. If GH_RELEASES_MANAGER_APP_ID / GH_RELEASES_MANAGER_APP_PRIVATE_KEY secrets are missing or empty, this action fails the job before the later fallback to github.token can run. Restore the previous if: guard (or make the step continue-on-error and handle the empty-token case) so staging CI still works when the GitHub App secrets aren't configured.
      - name: Generate GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ secrets.GH_RELEASES_MANAGER_APP_ID }}
          private-key: ${{ secrets.GH_RELEASES_MANAGER_APP_PRIVATE_KEY }}

.github/workflows/staging-ci.yml:236

  • Same issue here: generating the GitHub App token is unconditional, so missing/empty GH_RELEASES_MANAGER_* secrets will fail the gate job before the workflow can fall back to github.token. Add back an if: guard or make the step non-fatal and keep the fallback logic effective.
      - name: Generate GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ secrets.GH_RELEASES_MANAGER_APP_ID }}
          private-key: ${{ secrets.GH_RELEASES_MANAGER_APP_PRIVATE_KEY }}


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

Illia Polosukhin and others added 4 commits March 9, 2026 19:00
…709)

* feat: persist worker events to DB and fix activity tab rendering

In-process Worker (used by Scheduler::dispatch_job) now persists events
via save_job_event at key execution points: plan creation, LLM
responses, tool_use, tool_result, and job completion/failure/stuck.
Event data shapes match the container worker format so the gateway
activity tab renders them correctly.

Frontend: tool_result errors now show a red X icon with danger styling
instead of a silent empty output. The result event falls back to the
error field when message is absent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: wire RoutineEngine into gateway for direct manual trigger firing

Replace the message-channel hack in routines_trigger_handler with a
direct call to RoutineEngine::fire_manual(), ensuring FullJob routines
dispatch correctly when triggered from the web UI. Inject the engine
into GatewayState from Agent::run after construction.

Also persists user_id in save_job for both PG and libSQL backends,
removes the source='sandbox' filter so all jobs are visible, and
exposes job_id on RoutineRunInfo for the frontend job link.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove stale gateway_state argument from Agent::new test call sites

The gateway_state parameter was removed from Agent::new during rebase
(replaced by post-construction set_routine_engine_slot), but three test
call sites still passed the extra None argument.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review — restore sandbox source filter, remove blank lines

- Revert removal of `source = 'sandbox'` filter in all SandboxStore
  queries (8 sites across PG and libSQL). Sandbox-specific APIs should
  stay scoped to sandbox jobs; unified job listing for the Jobs tab
  should use a separate query path.
- Remove extra blank lines in agent_loop.rs and worker.rs that caused
  formatting CI failure.

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review — regenerate Cargo.lock, add user_id regression test

- Regenerate Cargo.lock from main's lockfile to eliminate dependency
  version downgrades (anyhow, syn, etc.) that were churn from rebase.
- Add regression test verifying user_id round-trips through save_job
  and get_job in the libSQL backend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: remove trailing blank line in libsql jobs.rs

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add Postgres-side regression test for user_id persistence in save_job

Mirrors the existing libSQL test (test_save_job_persists_user_id) for the
Postgres backend. Gated behind #[cfg(feature = "postgres")] + #[ignore]
since it requires a running PostgreSQL instance (integration tier).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ncel (#698)

Jobs could enter infinite retry loops because: (1) no token budget was
enforced, (2) iteration cap marked jobs as Stuck (allowing self-repair to
restart them), and (3) the web UI cancel button only updated the DB without
stopping the running worker.

- Add `max_tokens_per_job` config (settings.json + AGENT_MAX_TOKENS_PER_JOB
  env var, default 0 = unlimited) with per-job metadata override
- Track token usage after respond_with_tools() and fail the job on budget
  exceeded
- Change iteration cap and persistent rate limiting from mark_stuck to
  mark_failed, preventing self-repair restart loops
- Fix web cancel handler to call scheduler.stop() which updates in-memory
  state AND aborts the worker task, falling back to DB-only update

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…check

- Cancel handler now always persists Cancelled to DB regardless of whether
  scheduler.stop() ran, fixing the edge case where stop() returns Ok(())
  for jobs not in the scheduler map
- Collapse nested ifs per clippy (let-chains)
- Add NOTE comment about select_tools() not exposing TokenUsage

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ilblackdragon
ilblackdragon force-pushed the fix/698-job-budget-iteration-cap-cancel branch from f7cfbeb to ab861a5 Compare March 10, 2026 02:03
@github-actions github-actions Bot added scope: setup Onboarding / setup and removed risk: medium Business logic, config, or moderate-risk modules labels Mar 10, 2026
@ilblackdragon
ilblackdragon deleted the fix/698-job-budget-iteration-cap-cancel branch March 10, 2026 02:19
@zmanian zmanian mentioned this pull request Mar 11, 2026
@github-actions github-actions Bot mentioned this pull request Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: high Safety, secrets, auth, or critical infrastructure scope: agent Agent core (agent loop, router, scheduler) scope: channel/web Web gateway channel scope: ci CI/CD workflows scope: config Configuration scope: setup Onboarding / setup size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Jobs: no token budget or iteration cap; cancel/stop button doesn't work

3 participants