From d4dc1992377c963723ccf2810100feae54ef7814 Mon Sep 17 00:00:00 2001 From: vikramlc Date: Thu, 10 Sep 2026 08:07:04 +0530 Subject: [PATCH 1/4] fix(odin): Fix oversize metadata field truncation issue --- cognite/extractorutils/unstable/core/base.py | 11 ++++++----- tests/test_unstable/test_action_dispatch.py | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cognite/extractorutils/unstable/core/base.py b/cognite/extractorutils/unstable/core/base.py index d01b1f3b..73f0ffd3 100644 --- a/cognite/extractorutils/unstable/core/base.py +++ b/cognite/extractorutils/unstable/core/base.py @@ -784,9 +784,9 @@ def _handle_custom_action(self, action: Action) -> None: external_id=action.external_id, status=completed_status, result_message=truncate_message( - f"Action '{custom.name}' {outcome}, but metadata field(s) " - f"{', '.join(oversized_fields)} exceeded the {MAX_METADATA_VALUE_BYTES}-byte-per-value " - f"limit and were dropped from the reported result" + f"Action '{custom.name}' {outcome}, but {len(oversized_fields)} metadata field(s) " + f"exceeded the {MAX_METADATA_VALUE_BYTES}-byte-per-value limit and were dropped from " + f"the reported result: {', '.join(oversized_fields)}" ), result_metadata=filtered_metadata, ) @@ -813,8 +813,9 @@ def _handle_custom_action(self, action: Action) -> None: str(e) if not oversized_fields else truncate_message( - f"{e} (additionally, metadata field(s) {', '.join(oversized_fields)} exceeded " - f"the {MAX_METADATA_VALUE_BYTES}-byte-per-value limit and were dropped)" + f"{e} (additionally, {len(oversized_fields)} metadata field(s) exceeded the " + f"{MAX_METADATA_VALUE_BYTES}-byte-per-value limit and were dropped: " + f"{', '.join(oversized_fields)})" ) ), result_metadata=filtered_metadata, diff --git a/tests/test_unstable/test_action_dispatch.py b/tests/test_unstable/test_action_dispatch.py index 1d29dddc..dbf91d7a 100644 --- a/tests/test_unstable/test_action_dispatch.py +++ b/tests/test_unstable/test_action_dispatch.py @@ -407,6 +407,9 @@ def target(ctx: ActionContext) -> None: assert final.result_message is not None assert len(final.result_message) <= MAX_MESSAGE_LENGTH assert final.result_message.endswith("...") + # Regression: the explanation must survive truncation even when the field-name list doesn't. + assert "exceeded" in final.result_message + assert "dropped" in final.result_message def test_oversized_action_error_with_long_message_truncates_instead_of_crashing() -> None: From 875cfb61b027e49b1cdb13a081fa1a469c1ac445 Mon Sep 17 00:00:00 2001 From: vikramlc Date: Thu, 10 Sep 2026 08:19:46 +0530 Subject: [PATCH 2/4] refactor(odin): Refactor the result message for oversize fields --- cognite/extractorutils/unstable/core/base.py | 6 +++--- tests/test_unstable/test_action_dispatch.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cognite/extractorutils/unstable/core/base.py b/cognite/extractorutils/unstable/core/base.py index 73f0ffd3..9b932b60 100644 --- a/cognite/extractorutils/unstable/core/base.py +++ b/cognite/extractorutils/unstable/core/base.py @@ -813,9 +813,9 @@ def _handle_custom_action(self, action: Action) -> None: str(e) if not oversized_fields else truncate_message( - f"{e} (additionally, {len(oversized_fields)} metadata field(s) exceeded the " - f"{MAX_METADATA_VALUE_BYTES}-byte-per-value limit and were dropped: " - f"{', '.join(oversized_fields)})" + f"{len(oversized_fields)} metadata field(s) exceeded the " + f"{MAX_METADATA_VALUE_BYTES}-byte-per-value limit and were dropped. " + f"Action failed: {e}. Dropped fields: {', '.join(oversized_fields)}" ) ), result_metadata=filtered_metadata, diff --git a/tests/test_unstable/test_action_dispatch.py b/tests/test_unstable/test_action_dispatch.py index dbf91d7a..ab376663 100644 --- a/tests/test_unstable/test_action_dispatch.py +++ b/tests/test_unstable/test_action_dispatch.py @@ -431,6 +431,10 @@ def target(ctx: ActionContext) -> None: assert failed.result_message is not None assert len(failed.result_message) <= MAX_MESSAGE_LENGTH assert failed.result_message.endswith("...") + # Regression: the metadata-dropped explanation must survive truncation even when the + # (unbounded) ActionError message itself is long enough to be truncated away. + assert "exceeded" in failed.result_message + assert "dropped" in failed.result_message def test_custom_action_receives_call_metadata_in_context() -> None: From 40b3c17a99985f2878f0c3700466568e4501af7f Mon Sep 17 00:00:00 2001 From: vikramlc Date: Fri, 11 Sep 2026 08:23:45 +0530 Subject: [PATCH 3/4] fix(odin): Truncate error to include dropped fields --- cognite/extractorutils/unstable/core/base.py | 3 ++- tests/test_unstable/test_action_dispatch.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cognite/extractorutils/unstable/core/base.py b/cognite/extractorutils/unstable/core/base.py index 9b932b60..6e38b9c9 100644 --- a/cognite/extractorutils/unstable/core/base.py +++ b/cognite/extractorutils/unstable/core/base.py @@ -815,7 +815,8 @@ def _handle_custom_action(self, action: Action) -> None: else truncate_message( f"{len(oversized_fields)} metadata field(s) exceeded the " f"{MAX_METADATA_VALUE_BYTES}-byte-per-value limit and were dropped. " - f"Action failed: {e}. Dropped fields: {', '.join(oversized_fields)}" + f"Action failed: {truncate_message(str(e), max_length=500)}. " + f"Dropped fields: {', '.join(oversized_fields)}" ) ), result_metadata=filtered_metadata, diff --git a/tests/test_unstable/test_action_dispatch.py b/tests/test_unstable/test_action_dispatch.py index ab376663..eca78e8c 100644 --- a/tests/test_unstable/test_action_dispatch.py +++ b/tests/test_unstable/test_action_dispatch.py @@ -430,11 +430,13 @@ def target(ctx: ActionContext) -> None: assert failed.result_metadata == {"error_type": "invalid_parameter"} assert failed.result_message is not None assert len(failed.result_message) <= MAX_MESSAGE_LENGTH - assert failed.result_message.endswith("...") # Regression: the metadata-dropped explanation must survive truncation even when the - # (unbounded) ActionError message itself is long enough to be truncated away. + # ActionError message itself is long enough to need truncating on its own. assert "exceeded" in failed.result_message assert "dropped" in failed.result_message + # Regression: str(e) is bounded before combining, so there's still room left for the + # dropped-field-name list even when the ActionError message alone is very long. + assert "error_detail" in failed.result_message def test_custom_action_receives_call_metadata_in_context() -> None: From d3f09ce06006e6c500a184af517f4c26214b0a97 Mon Sep 17 00:00:00 2001 From: vikramlc Date: Fri, 11 Sep 2026 08:28:22 +0530 Subject: [PATCH 4/4] fix(odin): Strengthen the assertion for metadata fields --- tests/test_unstable/test_action_dispatch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_unstable/test_action_dispatch.py b/tests/test_unstable/test_action_dispatch.py index eca78e8c..635f1e6a 100644 --- a/tests/test_unstable/test_action_dispatch.py +++ b/tests/test_unstable/test_action_dispatch.py @@ -407,8 +407,9 @@ def target(ctx: ActionContext) -> None: assert final.result_message is not None assert len(final.result_message) <= MAX_MESSAGE_LENGTH assert final.result_message.endswith("...") - # Regression: the explanation must survive truncation even when the field-name list doesn't. - assert "exceeded" in final.result_message + # Regression: the explanation, including the count, must survive truncation even when the + # field-name list doesn't. + assert "45 metadata field(s) exceeded" in final.result_message assert "dropped" in final.result_message