fix(cron): stamp + replay creator sender/role across cron fires#1129
fix(cron): stamp + replay creator sender/role across cron fires#1129mozaa-solana wants to merge 1 commit into
Conversation
Cron jobs created in a group context (group-scope UserID like
"group:telegram:-100…") consistently fail group-scope permission
checks once they fire — the scheduled agent's turn ends up with an
empty SenderID, and any write_file / cron mutation inside that turn
trips:
permission denied: system context cannot ... in group chats.
If this is a legitimate user action, ensure the acting sender is
preserved through the tool chain.
Root cause: gateway_cron.go built RunRequest with UserID=job.UserID
but no SenderID, so loop_context.injectContext skipped WithSenderID
and the agent ran with no human attribution. UserID alone is the
group scope, not a real user.
Fix: capture the human's sender + role at cron-create time, replay
them at every fire.
- store.CronPayload: new CreatorSenderID + CreatorRole fields
(JSON-serialized into existing payload column — no migration).
- CronStore.AddJob: signature gains creatorSenderID + creatorRole;
PG + sqlite impls thread them into the new payload fields.
- tools/cron.go handleAdd: read SenderIDFromContext +
RoleFromContext, drop synthetic prefixes (subagent:, teammate:,
ticker:, system:, notification:, session_send_tool) so we never
attribute future fires to a system component, then call AddJob
with the captured creator.
- cmd/gateway_cron.go: pass job.Payload.CreatorSenderID +
CreatorRole into RunRequest.SenderID + Role each fire. Empty
values preserve prior behaviour (DM crons, system-installed
crons, pre-fix crons all keep the deny-on-empty guard at fire
time — no security relaxation).
- gateway/methods/cron.go (WS RPC): explicitly passes empty
creator strings — there is no separate "Telegram numeric sender"
upstream of the WS handler, so WS-installed crons that need to
fire in group chats should be created via a Telegram-rooted
flow (ctx-aware) instead.
Tests:
- internal/tools/cron_creator_propagation_test.go: real-sender
propagation + synthetic-sender drop coverage (table-driven).
- internal/gateway/methods/cron_test.go: stub updated to match
new AddJob signature, captures creator strings into Payload.
This is the cron-firing counterpart to the team-task announce
attribution fix — together they close the "system context cannot
write" denial across both re-ingress paths a Lead-style agent uses.
|
Maintainer recheck: current PR head still does not compile as-is. The public CronStore interface changed to AddJob(..., creatorSenderID, creatorRole), but existing store/integration tests and invariant tests still call the old 9-arg signature. A plain repo-wide grep shows old calls remain in internal/store/sqlitestore/cron_crud_test.go, tests/integration/v3_cron_store*_test.go, and tests/invariants/tenant_isolation_test.go, so go test ./... will fail once those packages are included. Targeted checks I ran:
Please update all remaining AddJob call sites to pass explicit creatorSenderID/creatorRole values, usually empty strings for existing tests unless the test is specifically asserting creator propagation. After that, rerun the full store/integration test set. |
mrgoonie
left a comment
There was a problem hiding this comment.
Blocking for now: this changes the CronStore.AddJob interface but leaves old-signature call sites in tests/integration/invariants. That means the branch will fail full compilation/test coverage outside the narrow package subset.
Evidence:
grep -R "AddJob(ctx" -n . --exclude-dir=.giton the PR branch still finds old 9-arg calls in:internal/store/sqlitestore/cron_crud_test.gotests/integration/v3_cron_store_test.gotests/integration/v3_cron_store_ext_test.gotests/invariants/tenant_isolation_test.go
- The implementation call sites in
internal/tools/cron.goandinternal/gateway/methods/cron.gowere updated, but these tests were not. - Targeted package checks passed for
./cmd ./internal/tools ./internal/gateway/methods, so the core idea is plausible; this is a completion/compile blocker.
Please update all remaining AddJob test call sites with explicit creatorSenderID, creatorRole arguments (empty strings for legacy cases), then rerun the full cron/store/integration test coverage.
Summary
Cron jobs created in a group context (UserID like `group:telegram:-100…`) consistently fail group-scope permission checks the moment they fire — the scheduled agent ends up with an empty SenderID, and any `write_file` or `cron` mutation inside that turn trips:
Root cause: `gateway_cron.go` built the RunRequest with `UserID = job.UserID` but no `SenderID`. `loop_context.injectContext` skips `WithSenderID` when `req.SenderID` is empty, so the agent's resumed ctx had no human attribution. `UserID` alone is the group scope, not a real user.
Fix
Capture the human's sender + role at cron-create time; replay them at every fire.
Failure repro
After this fix the same flow:
1-3. As above; job now stores `Payload.CreatorSenderID = ""`
4. Cron fires with `req.SenderID = ""` → `write_file` succeeds with attributed audit trail.
Test plan
Related
Companion fix to #1128 (team-task announce sender propagation). Together they close the "system context cannot write" denial across both re-ingress paths a Lead-style agent uses inside a daily-cycle workflow.