Skip to content

Keep task errors when exception details exceed the database column - #557

Open
fabi200123 wants to merge 1 commit into
cloudbase:mainfrom
fabi200123:truncate-task-exception-details
Open

fabi200123 wants to merge 1 commit into
cloudbase:mainfrom
fabi200123:truncate-task-exception-details

Conversation

@fabi200123

Copy link
Copy Markdown
Contributor

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.

A long command transcript makes the status update fail, so the task stays running instead of recording the failure.

@Cristi1324 Cristi1324 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +18 to +21
# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

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)

Suggested change
# 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

Comment thread coriolis/db/api.py
Comment on lines +910 to +913
if not exception_details:
return exception_details

max_length = models.MAX_EXCEPTION_DETAILS_LENGTH

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

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)

Suggested change
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

Comment on lines +948 to +958
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))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

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)

Suggested change
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

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