Skip to content

Keep flash messages in the session, not the store - #3

Merged
eliasjpr merged 1 commit into
masterfrom
claude/azu-crystal-framework-plan-phqt6e
Oct 22, 2026
Merged

eliasjpr merged 1 commit into
masterfrom
claude/azu-crystal-framework-plan-phqt6e

Conversation

@eliasjpr

Copy link
Copy Markdown
Contributor

Summary

Phase 0, item 12 of the plan (second part), and the follow-up flagged in #2.

A Store is shared by every request but held one Flash. A message one visitor set for their next request was therefore shown to whichever visitor made the next request: one user's "Welcome back, alice" (or an error message with personal data) could be rendered for another.

  • Session::Base#flash holds the flash. It was already JSON::Serializable but was never saved anywhere, so it now travels with the session through every store (memory, Redis, cookie).
  • Store#flash returns the current session's flash.
  • It is rotated (next → now) when the session it belongs to is loaded, so a message reaches only the visitor it was set for and lasts for exactly one of their requests.

Specs

spec/flash_isolation_spec.cr: a message set in Alice's request is not shown to Bob's request and is shown to Alice's next one (fails if the flash is shared again); a message lasts for exactly one request; the flash round-trips through the session's JSON. store_spec's "rotates flash on load" encoded the bug (it set a flash on a store with no session and expected rotation); it now rotates the flash of a loaded session.

crystal tool format --check is clean; 483 examples, 0 failures on Crystal 1.22.0.

Behaviour change

Code that wrote to store.flash before a session existed for the request now writes to the (new) current session, which SessionHandler saves as usual. Custom Session::Base subclasses with their own flash member will clash.

Still to do from the plan

CookieStore#cookies is still per-request state kept on the shared store; regenerate_id on authentication; refusing the default secret outside development; and a real client binding (validate_session_binding is a stub).


Generated by Claude Code

A store is shared by every request but held one Flash, so a message one
visitor set for their next request was shown to whichever visitor made
the next request. Flash was already serializable but was never saved.

The flash now belongs to the session (Session::Base#flash), is saved with
it by every store, and is rotated when the session it belongs to is
loaded, so a message reaches only the visitor it was set for and lasts for
exactly one of their requests. Store#flash returns the current session's.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HT5gvYGR83U2M4DyA6NA6f
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown
Contributor Author

The test job passed on this head. The release check is red, and that is not this PR's change: it fails at the create-release step with Validation Failed: already_exists / tag_name (Release v1.0.32). The workflow runs release on pull requests as well as on pushes, and the version in shard.yml already has a tag, so the step cannot succeed on any PR until the version is bumped. I have not changed the workflow here. Gating release on the push event (or on the tag not existing) would fix it for every PR; I can do that in a separate change if you want it.


Generated by Claude Code

@eliasjpr
eliasjpr merged commit 6d0bcbd into master Oct 22, 2026
5 of 6 checks passed
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.

2 participants