rubyforgood / rubyforgood/awbw
Ahoy lifecycle tracking silently drops changes made with no Current actor
Nobody has claimed this yet.
- Dominant language
- Ruby
- Stars
- 15
- Forks
- 26
- Avg merge
- 12h 42m
- Merged PRs (30d)
- 242
Description
🤖 From Claude:
AhoyTrackable (included in ApplicationRecord) tracks create/update/destroy for every model, but track_lifecycle_event opens with:
return unless Current.user || Current.source
Current.user is only set for a signed-in request (ApplicationController#set_current_user), and Current.source is set in exactly two places (public_forms_controller, events/public_registrations_controller). Everything else that writes without a signed-in user leaves no trace at all — no event, no error, no log line.
Verified
Calling scholarship.accept_agreement!(by: "recipient") with no actor set:
| Actor | Buffered events |
|---|---|
| none | [] |
Current.user set |
update.scholarship, create.scholarship_agreement_response |
Current.source set |
update.scholarship, create.scholarship_agreement_response |
Where this bites today
- Scholarship accept/decline on the public callout.
Events::CalloutsControllerdoesskip_before_action :authenticate_user!and never setsCurrent.source, so a logged-out recipient accepting or declining their agreement produces no Ahoy event. The bespokescholarship_agreement_responseslog is currently the only record of it. - Anything outside a request — jobs, rake tasks, console, webhooks (Stripe). These are dropped twice over: the actor guard, and
Analytics::LifecycleBufferonly flushing inApplicationController'safter_action, so a buffered event outside a request is never written.
Recommendation
Close the request-shaped hole narrowly rather than removing the guard.
- Default a source for every actorless request — one line in
ApplicationController: setCurrent.sourcewhen there's no signed-in user, so any public flow (present or future) is covered without each controller having to remember. Named per controller where it matters, generic otherwise. - Opt non-request contexts in explicitly — jobs, webhooks, and rake tasks set their own
Current.source("stripe_webhook","job:x"), andLifecycleBufferneeds a flush path that doesn't depend on a controller. - Don't just delete the guard. Seeds, imports, and the test suite would start emitting events in volume, which is presumably why it's there.
Acceptance
- A logged-out recipient accepting a scholarship agreement produces an Ahoy event attributed to a named source.
- A model write inside a job produces an event (or is a documented, deliberate exclusion).
- Specs cover both, so the guard can't silently re-open the hole.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in ApplicationController, Events::CalloutsController, and Analytics::LifecycleBuffer to trace how Current.source is set and when lifecycle events are flushed. Reproduce the logged-out scholarship agreement flow and a model write outside a request, then add focused specs showing that the public action is attributed and non-request behavior is explicitly handled.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ruby
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100