From: Simon Glass <simon.glass@canonical.com> CI job traces can contain embedded null bytes, which cause a ValueError ("embedded null byte") when the prompt string is passed to the Claude Agent SDK subprocess. Strip null bytes from the trace after decoding. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- tools/pickman/ftest.py | 35 +++++++++++++++++++++++++++++++++++ tools/pickman/gitlab_api.py | 1 + 2 files changed, 36 insertions(+) diff --git a/tools/pickman/ftest.py b/tools/pickman/ftest.py index ced2c79ac87..5a4d0c433dc 100644 --- a/tools/pickman/ftest.py +++ b/tools/pickman/ftest.py @@ -5787,6 +5787,41 @@ class TestGetFailedJobs(unittest.TestCase): self.assertIn('line 499', result[0].log_tail) + @mock.patch.object(gitlab, 'get_remote_url', + return_value=TEST_SSH_URL) + @mock.patch.object(gitlab, 'get_token', return_value='test-token') + @mock.patch.object(gitlab, 'AVAILABLE', True) + def test_null_bytes_stripped(self, _mock_token, _mock_url): + """Test that null bytes in job logs are stripped""" + trace_bytes = b'before\x00after\nline2\x00end\n' + + mock_job = self._make_mock_job( + 1, 'build:sandbox', 'build', 'https://gitlab.com/job/1', + trace_bytes) + + mock_full_job = mock.MagicMock() + mock_full_job.trace.return_value = trace_bytes + + mock_pipeline = mock.MagicMock() + mock_pipeline.jobs.list.return_value = [mock_job] + + mock_project = mock.MagicMock() + mock_project.pipelines.get.return_value = mock_pipeline + mock_project.jobs.get.return_value = mock_full_job + + mock_glab = mock.MagicMock() + mock_glab.projects.get.return_value = mock_project + + with mock.patch('gitlab.Gitlab', return_value=mock_glab): + with terminal.capture(): + result = gitlab.get_failed_jobs('ci', 100) + + self.assertEqual(len(result), 1) + self.assertNotIn('\0', result[0].log_tail) + self.assertIn('beforeafter', result[0].log_tail) + self.assertIn('line2end', result[0].log_tail) + + class TestBuildPipelineFixPrompt(unittest.TestCase): """Tests for build_pipeline_fix_prompt function.""" diff --git a/tools/pickman/gitlab_api.py b/tools/pickman/gitlab_api.py index d9635d392ee..81918c80c3c 100644 --- a/tools/pickman/gitlab_api.py +++ b/tools/pickman/gitlab_api.py @@ -478,6 +478,7 @@ def get_failed_jobs(remote, pipeline_id, max_log_lines=200): full_job = project.jobs.get(job.id) try: trace = full_job.trace().decode('utf-8', errors='replace') + trace = trace.replace('\0', '') lines = trace.splitlines() log_tail = '\n'.join(lines[-max_log_lines:]) except (AttributeError, gitlab.exceptions.GitlabError): -- 2.43.0