fix error:stale session error: last update time from request is older…
Fix: Truncate event timestamp to millisecond precision to prevent stale session errors
The MySQL datetime(3) column type only supports millisecond precision (3 decimal places). When storing timestamps with nanosecond precision, MySQL rounds the value, which can cause the stored time to be slightly greater than the original in-memory time.
This rounding issue triggers false "stale session error" because the comparison in applyEvent() uses UnixNano():
if storageUpdateTime > sessionUpdateTime {
return fmt.Errorf("stale session error: ...")
}
Solution: Truncate the event timestamp to millisecond precision before persisting, ensuring consistency between in-memory and database values:
event.Timestamp = time.UnixMilli(event.Timestamp.UnixMilli())
This eliminates sub-millisecond precision that would otherwise be lost (and potentially rounded up) during database storage.
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).
View this failed invocation of the CLA check for more information.
For the most up to date status, view the checks section at the bottom of the pull request.
Summary of Changes
Hello @huangbug, I'm Gemini Code Assist[^1]! 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 resolves an issue where a 'stale session error' would incorrectly trigger due to how timestamps are handled. Go's time.Time can store nanosecond precision, but MySQL's datetime(3) column type only supports millisecond precision. When a nanosecond timestamp was stored, MySQL would round it, potentially making the stored value slightly greater than the original in-memory timestamp. This discrepancy led to false positives during comparison checks. The fix ensures that event timestamps are truncated to millisecond precision before being written to the database, thereby aligning the precision and preventing these erroneous stale session errors.
Highlights
- Stale Session Error Fix: Addresses a 'stale session error' caused by a precision mismatch between Go's nanosecond timestamps and MySQL's millisecond-precision
datetime(3)column type. - Timestamp Truncation: The solution involves explicitly truncating event timestamps to millisecond precision before persistence, ensuring consistency between in-memory values and database storage.
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 :thumbsup: and :thumbsdown: 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.
[^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.
Hi @huangbug, thank you for bringing this up to our attention. Instead of truncating the saved value, we would prefer to add the respective tolerance to the "stale session" validation, since this does not affect every database? Alternatively does the MYSQL driver you're using allow for configuring datetime precision?
CREATE TABLE `sessions` (
`app_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`state` longtext COLLATE utf8mb4_unicode_ci,
`create_time` datetime(3) DEFAULT NULL,
`update_time` datetime(3) DEFAULT NULL,
PRIMARY KEY (`app_name`,`user_id`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Hi @baptmont , from the table definition, we can see that the update_time field in the default sessions table only supports millisecond precision, and MySQLâs DATETIME type does not support nanosecond precision. Without fixing this issue, the error mentioned above will frequently occur when executing runner.run(xxx).
What if we do this conversion to miliseconds only in the applyEvent()?
if storageUpdateTime > sessionUpdateTime {
return fmt.Errorf("stale session error: ...")
}
This way we will support the case when db provider has no nanos support.
Hi @dpasiukevich, to ensure that the update_time field in the sessions table maintains millisecond precision, the timestamp must be converted to millisecond precision before executing applyEvent(). Otherwise, inserting an event and executing applyEvent() to update update_time will result in rounding, causing a loss of precision.
For example:
UPDATE sessions
SET update_time = '2025-12-09 16:30:28.4915'
WHERE app_name = 'role-play-chatbot-dialogue'
AND user_id = 'test_1765269021'
AND id = 'test_1765269021';
After execution, the update_time in the sessions table will be updated to:
2025-12-09 16:30:28.492
As shown, the portion exceeding millisecond precision is rounded.
Honestly, I don't like the idea to have millisecond precision. If there are 2 agents working on the same session (e.g. main and sub agent), and they generated 2 events it will have a high chance to race (when event from one agent overwrites the event from the other, because both are in the same millisecond).
@huangbug could you verify if you use e.g precision 6 for time? https://github.com/go-gorm/mysql by setting mysql.Config. DefaultDatetimePrecision?
And if it sounds ok, for database provider we can set this precision in gorm model tag for session timestamp: https://github.com/google/adk-go/blob/0d0062df20e343382169205a1bd15a48b873062a/session/database/storage_session.go#L35
@huangbug @baptmont WDYT?
The bumping precision example: https://github.com/google/adk-go/compare/main...dpasiukevich:adk-go:db_session_precision
@huangbug could you check if this looks good?
Hi @dpasiukevich, If the update_time field is defined as datetime(6), you must ensure that the value written to the sessions table uses microsecond precision rather than nanosecond precision. Otherwise, MySQL will still perform rounding during insertion, causing precision loss and leading to the same issue. Importantly, the rounding is performed by MySQL itself, not by GORM.
Correct.
I've created a branch with the required changes: https://github.com/google/adk-go/compare/main...dpasiukevich:adk-go:db_session_precision
- It has
gorm:"precision:6"for time.Time type for all entities. So now mysql driver should create datetime(6) with microsecond precision. - There's a check in the current code which verifies if in-memory ADK object is in-sync with stored session. Which is failing right now in your case because MySQL ceiled it up. So in-memory object is treated as "outdated" because it kept the nanosecond precision in go code. I've updated this check to round both stored_session and inmemory object to be both in milliseconds to fix this.
Could I ask to PTAL the code if possible?
Thanks!
Hi @dpasiukevich, I've reviewed the changes and everything looks good to me.
@huangbug I've added https://github.com/google/adk-go/pull/414 to explicitly set microsecond level precision. Thank you for noticing this!