Skip to content

fix(cron): agent tools can no longer turn on a job's auto-approve - #24352

Merged
kyleseaman merged 1 commit into
mainfrom
fix/cron-auto-approval-owner-only
Oct 29, 2026
Merged

kyleseaman merged 1 commit into
mainfrom
fix/cron-auto-approval-owner-only

Conversation

@iamwhatever

@iamwhatever iamwhatever commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: Agent tools and internal-secret callers can no longer turn on a cron job's auto-approve; the owner turns it on from the Schedule page.

A job with approval_mode="auto" approves every tool call of every later run with nobody watching. Today the cron tools (cron_add / cron_update) accept it, and the dashboard cron write routes skip the owner check for a request that carries no app claim.

Why it matters

An auto job keeps running unattended on a schedule. Turning that on should be the owner's choice, made where the owner can see it.

Not a goal

  • The job store file (crons.json) and who can write it directly. Out of scope here and tracked separately.
  • The approval mode of sub-agents started by a cron session that stays running across fires. Out of scope here.
  • No change to how an auto job runs, or to the governance checks that still apply under auto.
  • No new confirmation dialog, stored signature or settings for auto-approve.
  • No change to the run/cancel/ack/to-chat routes, which do not write approval_mode.

What changed (motivation → approach → change)

  • Cron tools. cron_add and cron_update refuse approval_mode="auto" and point to the Schedule page. "" still works, so a tool can turn auto off. The gateway's /api/crons/tools route runs the same dispatch, so the refusal holds there too. The tool descriptions (mcp_cron_tool_list.py) say so up front.
  • Edits to an auto job. A cron_update that changes message, agent, model, schedule, channel or thread_ts now also writes approval_mode="" in the same update, so the owner re-enables auto for the changed job; a rename keeps it. Otherwise an owner-approved auto job could be changed to do something else and stay auto.
  • Dashboard routes. Create, delete, batch delete, update and enable gated on request.get("app") == "", which skipped the owner check for a request with no app claim. They now use not request.get("app"), the shape the file and workflow routes use. App tokens keep their manifest scope.

Docs: the cron user guide (src/kiro_crew/docs/cron-and-scheduling.md) and the cron spec say who turns auto on. Test fixtures that called these routes on a bare app now carry the owner identity (dashboard_owner_helpers.as_owner).

Backwards compatibility

Breaking: an agent can no longer create or switch a job to approval_mode="auto" through cron_add / cron_update; it creates the job and the user turns auto on in the Schedule page. A cron_update that changes what an auto job does now turns auto off. Internal-secret callers of the five cron write routes now get 403 owner_only.

Writer sweep on origin/main @ 2356a1b:

  • website/src/components/JobForm.tsx / ApprovalModePicker.tsx -- Schedule page, owner cookie -- unchanged.
  • Change cards replay /api/crons from the owner's browser (dashboard/handlers/change_cards.py) -- owner cookie, unchanged (test_change_card_one_shot.py).
  • apps/cron_sdk.py -- App SDK -- unchanged.
  • cli_commands_cron.py -- kirocrew cron add --approval-mode auto -- unchanged.
  • Internal-secret callers of /api/crons: only /run (cron_trigger.py), /history (mcp_cron_list_render.py) and /tools (mcp_cron.py); none of the five gated routes.

Reader: src/kiro_crew/mcp_cron.py:_call_tool_inner -- chat -- cron_add / cron_update without "auto" work as before (test_cron_add_with_default_approval_still_works, test_cron_update_can_still_turn_auto_off)
Reader: src/kiro_crew/dashboard/handlers/cron_jobs.py:api_crons_create -- crew page -- the owner still creates an auto job (test_the_owner_still_creates_an_auto_job_from_the_schedule_page, test_r7_owner_gate_positive_controls.py)

Tests

  • test/test_cron_auto_approval_owner_only.py: internal-secret callers get owner_only on create/update/delete/batch delete/enable and nothing is written; the owner still creates an auto job; cron_add / cron_update refuse auto and write nothing; "" still works; auto can be turned off.
  • test_cron_auto_approval_owner_only.py (edits): each behaviour field turns auto off; a rename keeps it; an edit to a normal job says nothing about auto; the owner's PATCH keeps auto.
  • test_cron_approval_mode.py, test_validation_user_actions.py, test_mcp_cron.py: the old "tool sets auto" expectations now expect the refusal.

Proof: PROVEN test/test_cron_approval_mode.py::TestCronApprovalModeValidation::test_valid_auto, test/test_cron_auto_approval_owner_only.py::test_internal_secret_caller_is_refused_by_the_owner_gate[create], test/test_cron_auto_approval_owner_only.py::test_internal_secret_caller_is_refused_by_the_owner_gate[update], test/test_cron_auto_approval_owner_only.py::test_internal_secret_caller_is_refused_by_the_owner_gate[delete], test/test_cron_auto_approval_owner_only.py::test_internal_secret_caller_is_refused_by_the_owner_gate[batch] (head e57670c)

Manual verification

N/A — unit coverage drives each writer through the real handler or tool dispatch.

Related Issues

Closes #24345

Sibling sweep

Swept: git grep -n 'request.get("app") == ""' -- src/kiro_crew/dashboard/handlers/cron*.py
Sibling: src/kiro_crew/dashboard/handlers/cron_jobs.py -- changed here
Sibling: src/kiro_crew/dashboard/handlers/cron_runs.py -- not affected -- run/cancel/ack routes have an internal-secret caller and never write approval_mode
Swept: git grep -n 'approval_mode' -- src/kiro_crew/mcp_cron.py
Sibling: src/kiro_crew/mcp_cron.py -- changed here

Pattern harvest

Rule candidate: review-prompt
Pattern: an owner gate written as request.get("app") == "" skips callers that publish no app claim; use not request.get("app").

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Sensitive Change Review

  • No sensitive path changed; nothing to check.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

UX Review (Opus 5.5) — ⏭️ skipped

Revision d8f7bee4dcf1a30136629edbfc0b5d579a31e217 touches no user-facing surface (no changes under website/ or committed screenshots), so the UX review was skipped. Does not block merge.

@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Oct 26, 2026
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

First Principles Review (Opus 5.5) — ✅ PASS

Premise-level review of d8f7bee4dcf1a30136629edbfc0b5d579a31e217 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

First-Principles-Verdict: PASS

Nothing blocks. Every item is a security fix with a linked issue (#24345), and the agent turning on its own unattended approval falls inside the documented boundary that the agent is untrusted about its own governance ceiling.

Notes

  • Item 3 — symptom-level: the description says not request.get("app") is "the shape the file and workflow routes use", but dashboard/handlers/workflows.py still has request.get("app") == "" at 6 sites. Grepping request.get\("app"\) == "" also finds 3 non-cron sites with the same gate shape: side.py:1091, side.py:1399 and core_stt_access.py:191. Subtraction: drop the "same shape as the file and workflow routes" clause from the new cron_jobs.py comment, since the claim it cites does not hold.

What this change ships

Inventory (6 items) — 6 justified

Intent: stop an agent's cron tools (and internal-secret callers) from switching a cron job to unattended auto-approve; the owner does it from the Schedule page. FIX.

  1. cron_add / cron_update refuse approval_mode="auto" with an error pointing to the Schedule page; "" still works — justified
  2. An agent's cron_update that changes what an auto job does (message, agent, model, schedule, channel, thread) turns auto off and says so; a rename keeps it — justified
  3. Internal-secret callers of cron create/delete/batch delete/update/enable now get 403 owner_only — justified
  4. Tool descriptions for approval_mode say auto is owner-only — justified
  5. The cron user guide drops the "Via MCP: approval_mode=auto" example and says who turns auto on — justified
  6. The spec bullet in learn-cron-dashboard.md records the owner-only rule — justified

[FIRST-PRINCIPLES-REVIEWED] d8f7bee

Description read

[DESCRIPTION-READ] 7cf88b3021ede08409c2b644968b99c92153da79ebdf0175fd3480717ab71e1d

That sha256 names the PR description this verdict read. Recompute it with .github/scripts/pr-description-capture.sh; if it differs, the description changed after this verdict was formed and any finding drawn from the description is unproven.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Design Review (Opus 5.5) — ✅ PASS

Design-level review of d8f7bee4dcf1a30136629edbfc0b5d579a31e217 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

Design-Verdict: PASS

No design-level concerns. Turning auto-approve on now happens only through the owner-gated routes. The take-away readers I checked still work: the CLI calls CronService directly, and change cards replay from the owner's browser.

Notes

  • The reset-on-edit list (_AUTO_RESETTING_FIELDS) has to be kept up to date by hand. If a later cron_update field changes what a job does and isn't added to it, an owner-approved auto job could be changed and stay auto. A pin that checks the list against update_job's parameters would catch that.

[DESIGN-REVIEWED] d8f7bee

Description read

[DESCRIPTION-READ] b1309cd741249e59c14b462ba464dd2c78aa4806c15643c73f7791ed00138832

That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

GPT 6.1 Review — ✅ no blocking findings

GPT 6.1 completed its review of d8f7bee4dcf1a30136629edbfc0b5d579a31e217 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] d8f7bee

False positive or not applicable? A repository writer can comment:
/ai-review override gpt d8f7bee4dcf1a30136629edbfc0b5d579a31e217 false-positive: <reason, 20 to 500 characters>

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Opus 5.5 Review — ✅ no blocking findings

Reviewed d8f7bee4dcf1a30136629edbfc0b5d579a31e217 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] d8f7bee

Verdict parsed from the review's SHA-scoped output markers for commit d8f7bee4dcf1a30136629edbfc0b5d579a31e217.

False positive or not applicable? A repository writer can comment:
/ai-review override fable d8f7bee4dcf1a30136629edbfc0b5d579a31e217 false-positive: <reason, 20 to 500 characters>

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Oct 26, 2026
@iamwhatever iamwhatever changed the title fix(cron): only the owner turns on a job's auto-approve fix(cron): agent tools can no longer turn on a job's auto-approve Oct 26, 2026
@iamwhatever
iamwhatever force-pushed the fix/cron-auto-approval-owner-only branch from d8d0d98 to 3bf46dc Compare October 26, 2026 16:34
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 26, 2026
@iamwhatever

Copy link
Copy Markdown
Contributor Author

Rebased: main carries the fix for the red in test/test_crew_log_fullscan_guard.py (#24482, #24485) since this head ran (2356a1b -> 20c46e9); CI re-running.

@iamwhatever
iamwhatever force-pushed the fix/cron-auto-approval-owner-only branch from 3bf46dc to 270e16b Compare October 26, 2026 18:38
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 26, 2026
@iamwhatever
iamwhatever force-pushed the fix/cron-auto-approval-owner-only branch from 270e16b to d8450ee Compare October 26, 2026 20:50
@iamwhatever

Copy link
Copy Markdown
Contributor Author

Rebased: main carries the fix for the red in test/test_telegram_dispatcher.py (#24531) since this head ran (20c46e9 -> dbf207c); CI re-running.

@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 26, 2026
@iamwhatever
iamwhatever force-pushed the fix/cron-auto-approval-owner-only branch from 8b2b111 to f7ee4f4 Compare October 27, 2026 19:04
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Oct 27, 2026
@iamwhatever
iamwhatever force-pushed the fix/cron-auto-approval-owner-only branch from f7ee4f4 to 40a0e0c Compare October 27, 2026 19:24
@iamwhatever

iamwhatever commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor Author
  • span=02904f6955d9 Retained cron sessions keep auto-approval after a tool edit

fixed in 40a0e0c: cron_fire._acquire_with_model_fallback now takes "auto" off a session retained under the job key when the saved job is no longer auto, before get_or_create (which never lowers a policy). Sub-agents spawned that fire read the parent policy at spawn, so they no longer inherit auto. Pinned by test_cron_approval_mode.py::TestCronApprovalModeGateway::test_a_job_no_longer_auto_takes_auto_off_its_retained_session, which fails with the fix reverted.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

AI-review override not recorded: product-shape is not a release class. Use /ai-review override <fable|gpt|design|ux|first-principles|scope|all> <current-sha> <class>[ #N]: <reason, 20 to 500 characters>, where <class> is one of false-positive, not-applicable, pre-existing, evidence-supplied, infra-failure, reissue, deferred, owner-decision. What each class needs: docs/ci/ci-and-reviews.md#risk-release-records.

@iamwhatever

Copy link
Copy Markdown
Contributor Author

Rebased: main moved in src/kiro_crew/mcp_cron.py, mcp_cron_tool_list.py and the cron docs since this head ran (f265a52 -> 7b0ab42). This head also drops the retained-session change; that area is out of scope here. CI re-running.

@iamwhatever
iamwhatever force-pushed the fix/cron-auto-approval-owner-only branch from 40a0e0c to e57670c Compare October 29, 2026 06:59
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Oct 29, 2026
@iamwhatever
iamwhatever force-pushed the fix/cron-auto-approval-owner-only branch from e57670c to 279a00e Compare October 29, 2026 07:08
@iamwhatever

Copy link
Copy Markdown
Contributor Author

Rebased: main moved in docs/system-specs/modules/learn-cron-dashboard.md since this head ran (7b0ab42 -> 7df9837). The prior head's reviews read an older description that still named a dropped change; the current description matches the diff. CI re-running.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Oct 29, 2026
A cron job with approval_mode="auto" approves every tool call of every
later fire with nobody watching. The Schedule page is where the owner
turns that on, but the agent tools and internal-secret callers could set
it too:

- cron_add / cron_update accepted approval_mode="auto". They now refuse
  it with a message pointing at the Schedule page; "" (turn it off) is
  still accepted. The gateway's /api/crons/tools route runs the same
  dispatch, so it is refused there as well.
- cron_update kept an owner-set auto job on auto while changing its
  message, agent, model, schedule, channel or thread. Such an edit now
  writes approval_mode="" in the same update, so the owner re-enables
  auto for the changed job; a rename keeps it.

Tests that called these routes on a bare app now carry the owner identity
(dashboard_owner_helpers.as_owner).

Out of scope: the job store file itself, tracked separately.

Closes #24345
@iamwhatever

Copy link
Copy Markdown
Contributor Author

Rebased: main carries the mypy fix for _frame_parents (#25706) since this head ran (7df9837 -> a51058b); CI re-running.

@iamwhatever
iamwhatever force-pushed the fix/cron-auto-approval-owner-only branch from 279a00e to d8f7bee Compare October 29, 2026 14:36
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 29, 2026
@kyleseaman
kyleseaman enabled auto-merge (squash) October 29, 2026 16:29
@kyleseaman
kyleseaman merged commit a1d44a4 into main Oct 29, 2026
95 checks passed
@kyleseaman
kyleseaman deleted the fix/cron-auto-approval-owner-only branch October 29, 2026 16:31
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Oct 29, 2026
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.

Cron auto-approve can be turned on outside the Schedule page

2 participants