Repository navigation
Keep task errors when exception details exceed the database column - #557
fabi200123 wants to merge 1 commit into
Conversation
A long command transcript makes the status update fail, so the task stays running instead of recording the failure.
Cristi1324
left a comment
There was a problem hiding this comment.
Review, model: Grok 4.7
1 finding: 1 medium, confirmed in Python. The server effect is suspected.
Reviewed at ac37e980fd916f01b8905241781db20a678caf61. AI agents wrote these findings, and a person chose to post them. Suspected findings are unproven. Findings on the diff are inline comments.
Context: 001_initial.py creates task.exception_details as Text with mysql_charset='utf8'. models.py maps that column as Text. set_task_status writes it. No later migration changes the charset. The unit tests use SQLite. MariaDB was not run.
Verdict: not approved. The cap must be 65535 // 3, and a character above U+FFFF must be replaced with ? before the length check. The three comments are this finding. GitHub can commit the suggestions together.
| # MySQL TEXT holds at most 65,535 bytes. utf8mb4 can use 4 bytes per | ||
| # character, so this is the longest exception_details value guaranteed | ||
| # to fit in the column. | ||
| MAX_EXCEPTION_DETAILS_LENGTH = 65535 // 4 |
There was a problem hiding this comment.
Medium · confirmed · The cap uses 4 bytes per character on a utf8 column.
The diff sets MAX_EXCEPTION_DETAILS_LENGTH to 65535 // 4. A value of 20,000 ASCII characters is cut to 16,383 characters. Those 20,000 bytes fit in the TEXT column.
Why
The task column is charset utf8, so the longest safe value is 65,535 bytes divided by 3.
Added
- The cap is
65535 // 3. - The comment names charset
utf8.
Documentation
- MySQL 8.4 data type storage requirements —
TEXTstorage isL + 2bytes, whereL < 2^16. - MySQL 8.4 Unicode character sets —
utf8: A deprecated alias forutf8mb3. - The utf8mb3 character set — Requires a maximum of three bytes per multibyte character.
Evidence
001_initial.py:124 creates exception_details as sqlalchemy.Text, and 001_initial.py:128 sets mysql_charset='utf8'. models.py:21 sets the cap to 65535 // 4. The committed function cut 20,000 ASCII characters to 16,383. After this change those 20,000 characters stayed 20,000.
Proposed fix (version 1 of 1, accepted)
This version sets the cap to 65535 // 3 and names charset utf8.
Suggested change (accepted)
| # MySQL TEXT holds at most 65,535 bytes. utf8mb4 can use 4 bytes per | |
| # character, so this is the longest exception_details value guaranteed | |
| # to fit in the column. | |
| MAX_EXCEPTION_DETAILS_LENGTH = 65535 // 4 | |
| # MySQL TEXT holds at most 65,535 bytes. The task table uses charset utf8, | |
| # which stores at most 3 bytes per character. | |
| MAX_EXCEPTION_DETAILS_LENGTH = 65535 // 3 |
Discussion with the review agent
Version 1 was run. ruff format left the file unchanged, ruff check passed, and the test_api unit tests passed. No later version was written.
model: Grok 4.7
| if not exception_details: | ||
| return exception_details | ||
|
|
||
| max_length = models.MAX_EXCEPTION_DETAILS_LENGTH |
There was a problem hiding this comment.
Medium · confirmed · A character above U+FFFF is returned unchanged.
_fit_exception_details returns the original text when its length is within the cap. Ten U+10000 characters came back unchanged. Charset utf8 does not include that character. A MariaDB or MySQL run was not done, so a rejected status update stays suspected.
Why
A character above U+FFFF is outside this utf8 column, so the status update can still fail.
Added
- A character above U+FFFF is replaced with
?before the length check.
Documentation
- The utf8mb3 character set — Supports BMP characters only (no support for supplementary characters).
- MariaDB Unicode —
utf8is an alias forutf8mb3, and no supplementary characters are stored.
Evidence
api.py:915 returns exception_details when original_length is within MAX_EXCEPTION_DETAILS_LENGTH. With the committed cap, ten U+10000 characters were returned unchanged. After this change they became ten ? characters. The truncation loop is unchanged. The server was not run.
Proposed fix (version 1 of 1, accepted)
This version replaces a character above U+FFFF with ? before the length check.
Suggested change (accepted)
| if not exception_details: | |
| return exception_details | |
| max_length = models.MAX_EXCEPTION_DETAILS_LENGTH | |
| if not exception_details: | |
| return exception_details | |
| # Charset utf8 rejects a character above U+FFFF. | |
| exception_details = "".join( | |
| "?" if ord(c) > 0xFFFF else c for c in exception_details | |
| ) | |
| max_length = models.MAX_EXCEPTION_DETAILS_LENGTH |
Discussion with the review agent
Version 1 was run. ruff format left the file unchanged, ruff check passed, and the test_api unit tests passed. No later version was written.
model: Grok 4.7
| def test_fit_exception_details_stays_within_mysql_text_for_multibyte_text(self): | ||
| # U+10000 encodes to 4 bytes in UTF-8, the utf8mb4 worst case. | ||
| four_byte_char = "\U00010000" | ||
| details = four_byte_char * (models.MAX_EXCEPTION_DETAILS_LENGTH + 500) | ||
|
|
||
| fitted = api._fit_exception_details("task-id", details) | ||
|
|
||
| self.assertLessEqual(len(fitted), models.MAX_EXCEPTION_DETAILS_LENGTH) | ||
| self.assertLessEqual(len(fitted.encode("utf-8")), 65535) | ||
| self.assertTrue(fitted.startswith(four_byte_char)) | ||
| self.assertTrue(fitted.endswith(four_byte_char)) |
There was a problem hiding this comment.
Medium · confirmed · The multibyte test keeps a supplementary character.
The test builds the value from U+10000 and expects that character at both ends. That treats utf8mb4 as the column charset.
Why
The test must follow the utf8 column, which stores at most 3 bytes per character.
Added
- The long value uses U+4F60.
- One U+10000 character is expected to become
?.
Documentation
- The utf8mb3 character set — Requires a maximum of three bytes per multibyte character.
Evidence
test_api.py:949 names U+10000 as the utf8mb4 worst case, and test_api.py:957 expects the fitted text to start with that character. The updated test passed with the other test_api tests.
Proposed fix (version 1 of 1, accepted)
This version uses U+4F60 for the long value and expects one U+10000 character to become ?.
Suggested change (accepted)
| def test_fit_exception_details_stays_within_mysql_text_for_multibyte_text(self): | |
| # U+10000 encodes to 4 bytes in UTF-8, the utf8mb4 worst case. | |
| four_byte_char = "\U00010000" | |
| details = four_byte_char * (models.MAX_EXCEPTION_DETAILS_LENGTH + 500) | |
| fitted = api._fit_exception_details("task-id", details) | |
| self.assertLessEqual(len(fitted), models.MAX_EXCEPTION_DETAILS_LENGTH) | |
| self.assertLessEqual(len(fitted.encode("utf-8")), 65535) | |
| self.assertTrue(fitted.startswith(four_byte_char)) | |
| self.assertTrue(fitted.endswith(four_byte_char)) | |
| def test_fit_exception_details_stays_within_mysql_text_for_multibyte_text(self): | |
| # U+4F60 encodes to 3 bytes. That is the utf8 worst case. | |
| three_byte_char = "\u4f60" | |
| details = three_byte_char * (models.MAX_EXCEPTION_DETAILS_LENGTH + 500) | |
| fitted = api._fit_exception_details("task-id", details) | |
| self.assertLessEqual(len(fitted), models.MAX_EXCEPTION_DETAILS_LENGTH) | |
| self.assertLessEqual(len(fitted.encode("utf-8")), 65535) | |
| self.assertTrue(fitted.startswith(three_byte_char)) | |
| self.assertTrue(fitted.endswith(three_byte_char)) | |
| self.assertEqual("?", api._fit_exception_details("task-id", "\U00010000")) |
Discussion with the review agent
Version 1 was run. ruff format left the file unchanged, ruff check passed, and the test_api unit tests passed. No later version was written.
model: Grok 4.7
This PR fixes an issue where sometimes yum errors were exceeding the limit of columns in SQL and caused an issue.
Now, we truncate the error so that only the beginning and the end are kept: Coriolis describes the failure first, and the underlying command reports the actual error last.