Improve diagnostics for unreadable artifacts - #27201
Conversation
There was a problem hiding this comment.
Pull request overview
Improves ArtifactArchiver diagnostics when an AccessDeniedException occurs during artifact archiving by surfacing a concise, actionable build-log message (including denied path + workspace) while keeping stack traces in logger output.
Changes:
- Add a localized error message for access-denied artifact archiving failures, including workspace context and permission guidance.
- Update
ArtifactArchiverto throw anAbortExceptionwith the new message and log details atFINE. - Extend the existing Unix-only regression test to validate the new actionable message and associate it with JENKINS-21905 and JENKINS-27188.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/src/test/java/hudson/tasks/ArtifactArchiverTest.java | Updates regression test to assert the new access-denied diagnostic message. |
| core/src/main/resources/hudson/tasks/Messages.properties | Adds a new localized message template for access-denied archiving failures. |
| core/src/main/java/hudson/tasks/ArtifactArchiver.java | Enhances AccessDeniedException handling to emit a clearer AbortException message and log details at FINE. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } catch (AccessDeniedException e) { | ||
| LOG.log(Level.FINE, "Diagnosing anticipated Exception", e); | ||
| throw new AbortException(e.toString()); // Message is not enough as that is the filename only | ||
| String deniedPath = e.getFile() != null ? e.getFile() : e.toString(); |
There was a problem hiding this comment.
I saw no difference in the output with this change compared to the current code on the master branch. My test environment is included in gh-core-27188.zip
The steps that I took while trying to duplicate the issue:
- Download and unzip gh-core-27188.zip on my Linux computer
- Change to the gh-core-27188 directory and run
bash ./README - Open a web browser on port 8080 of the Linux computer
- Login with my Linux username ('mwaite') and the password 'p-' plus my username ('p-mwaite')
- Review the results of the job 'archiveArtifacts-fails-on-unreadable-files'. Confirm that it reports a stack trace:
Started by user SYSTEM
[Pipeline] Start of Pipeline
[Pipeline] node
Running on rhel-8-a.markwaite.net-mwaite in /home/mwaite/rhel-8-a.markwaite.net-mwaite/workspace/archiveArtifacts-fails-on-unreadable-files
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Archive an unreadable file)
[Pipeline] sh
+ date
[Pipeline] sh
+ date
+ chmod 000 date-file-2.txt
[Pipeline] archiveArtifacts
Archiving artifacts
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Also: Also: hudson.remoting.ProxyException: java.io.IOException: This archives contains unclosed entries.
at Jenkins v2.568.2//org.apache.tools.tar.TarOutputStream.finish(TarOutputStream.java:235)
at Jenkins v2.568.2//org.apache.tools.tar.TarOutputStream.close(TarOutputStream.java:252)
at Jenkins v2.568.2//hudson.util.io.TarArchiver.close(TarArchiver.java:128)
at Jenkins v2.568.2//hudson.FilePath.writeToTar(FilePath.java:3053)
Also: hudson.remoting.ProxyException: hudson.remoting.Channel$CallSiteStackTrace: Remote call to rhel-8-a.markwaite.net-mwaite
at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1916)
at hudson.remoting.UserRequest$ExceptionResponse.retrieve(UserRequest.java:384)
at hudson.remoting.Channel$2.adapt(Channel.java:1144)
at hudson.remoting.Channel$2.adapt(Channel.java:1140)
at hudson.remoting.FutureAdapter.get(FutureAdapter.java:66)
at hudson.FilePath.copyRecursiveTo(FilePath.java:2850)
at jenkins.model.StandardArtifactManager.archive(StandardArtifactManager.java:73)
at hudson.tasks.ArtifactArchiver.perform(ArtifactArchiver.java:258)
at PluginClassLoader for workflow-basic-steps//org.jenkinsci.plugins.workflow.steps.CoreStep$Execution.run(CoreStep.java:101)
at PluginClassLoader for workflow-basic-steps//org.jenkinsci.plugins.workflow.steps.CoreStep$Execution.run(CoreStep.java:71)
at PluginClassLoader for workflow-step-api//org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:49)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
at java.base/java.lang.Thread.run(Thread.java:1474)
hudson.remoting.ProxyException: java.io.IOException: entry 'date-file-2.txt' closed at '0' before the '29' bytes specified in the header were written
at Jenkins v2.568.2//org.apache.tools.tar.TarOutputStream.closeEntry(TarOutputStream.java:362)
at Jenkins v2.568.2//hudson.util.io.TarArchiver.visit(TarArchiver.java:121)
at Jenkins v2.568.2//hudson.util.DirScanner.scanSingle(DirScanner.java:52)
at Jenkins v2.568.2//hudson.FilePath$ExplicitlySpecifiedDirScanner.scan(FilePath.java:3734)
at Jenkins v2.568.2//hudson.FilePath.writeToTar(FilePath.java:3054)
at Jenkins v2.568.2//hudson.FilePath$CopyRecursiveRemoteToLocal.invoke(FilePath.java:3019)
at Jenkins v2.568.2//hudson.FilePath$CopyRecursiveRemoteToLocal.invoke(FilePath.java:3002)
at Jenkins v2.568.2//hudson.FilePath$FileCallableWrapper.call(FilePath.java:3655)
at hudson.remoting.UserRequest.perform(UserRequest.java:225)
at hudson.remoting.UserRequest.perform(UserRequest.java:50)
at hudson.remoting.Request$2.run(Request.java:391)
at hudson.remoting.InterceptingExecutorService.lambda$wrap$0(InterceptingExecutorService.java:81)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused: hudson.remoting.ProxyException: java.util.concurrent.ExecutionException: java.io.IOException: entry 'date-file-2.txt' closed at '0' before the '29' bytes specified in the header were written
at hudson.remoting.Channel$2.adapt(Channel.java:1146)
at hudson.remoting.Channel$2.adapt(Channel.java:1140)
at hudson.remoting.FutureAdapter.get(FutureAdapter.java:66)
at hudson.FilePath.copyRecursiveTo(FilePath.java:2850)
Also: hudson.remoting.ProxyException: org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: 7ff33e96-e9ff-45ce-9f71-0c717645a5c7
hudson.remoting.ProxyException: java.io.EOFException
at java.base/java.util.zip.GZIPInputStream.readUByte(GZIPInputStream.java:332)
at java.base/java.util.zip.GZIPInputStream.readUShort(GZIPInputStream.java:322)
at java.base/java.util.zip.GZIPInputStream.readHeader(GZIPInputStream.java:227)
at java.base/java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:86)
at java.base/java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:118)
at hudson.FilePath$TarCompression$2.extract(FilePath.java:890)
at hudson.FilePath.copyRecursiveTo(FilePath.java:2847)
at jenkins.model.StandardArtifactManager.archive(StandardArtifactManager.java:73)
at hudson.tasks.ArtifactArchiver.perform(ArtifactArchiver.java:258)
at PluginClassLoader for workflow-basic-steps//org.jenkinsci.plugins.workflow.steps.CoreStep$Execution.run(CoreStep.java:101)
at PluginClassLoader for workflow-basic-steps//org.jenkinsci.plugins.workflow.steps.CoreStep$Execution.run(CoreStep.java:71)
at PluginClassLoader for workflow-step-api//org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:49)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
at java.base/java.lang.Thread.run(Thread.java:1474)
Finished: FAILURE
The job definition that created that output is:
pipeline {
agent any
stages {
stage('Archive an unreadable file') {
steps {
sh 'date >> date-file.txt'
sh 'date >> date-file-${BUILD_ID}.txt && chmod 000 date-file-${BUILD_ID}.txt'
archiveArtifacts artifacts: 'date-file*.txt'
}
}
}
}
|
@MarkEWaite |
MarkEWaite
left a comment
There was a problem hiding this comment.
The modified lines in TarArchiver are not reached by any test in the ArtifactArchiverTest. When I use a debugger to run the automated tests that are changed in this pull request, with breakpoints on each line that was changed in TarArchiver, my breakpoints are never encountered by the automated test.
Please be sure that your automated tests execute the modified lines in the production code.
|
@MarkEWaite I also kept the existing |
|
Thanks. I don't understand why the changes to TarArchiver are needed, if the issue for the user is resolved by adjusting the exception in ArtifactArchiver. Can you explain in more detail why the changes to TarArchiver are required? |
|
@MarkEWaite TarArchiver previously wrote the entry header before opening the file. When opening the file failed, closeEntry() then failed because the declared number of bytes had not been written. That incomplete-entry exception masked the original AccessDeniedException, followed by the additional EOF/unclosed-entry errors seen in your reproduction. As a result, changing only the AccessDeniedException handling in ArtifactArchiver works when that exception survives, such as the original zero-byte test case, but not for the non-empty file from your reproduction. Opening the file before writing its tar header ensures that an unreadable file cannot create a partial entry. It also allows the original AccessDeniedException to reach ArtifactArchiver, where it can be converted into the actionable user-facing message. |
|
I misdiagnosed a failure to encounter my breakpoints in the TarArchiver changes. The TarArchiver class is run on the agent while my debugger was only attached to the Java process of the controller. I inserted logging statements into the TarArchiver class at your changes and confirmed that they are called on the agent. We'll need to run this change through the plugin compatibility tests and the acceptance test harness tests to assure that none of the larger test suites are broken by the change. |
|
@MarkEWaite |
MarkEWaite
left a comment
There was a problem hiding this comment.
2 changes for the issue references.
|
@MarkEWaite I also reran |
Generated with core pull request: * jenkinsci/jenkins#27206 Includes pull requests: * jenkinsci/jenkins#27205 * jenkinsci/jenkins#27201 * jenkinsci/jenkins#27193 * jenkinsci/jenkins#27163 * jenkinsci/jenkins#27088 * jenkinsci/jenkins#27083 * jenkinsci/jenkins#27067 * jenkinsci/jenkins#27065 * jenkinsci/jenkins#27063 * jenkinsci/jenkins#27032 * jenkinsci/jenkins#26923 * jenkinsci/jenkins#26922 * jenkinsci/jenkins#26913 * jenkinsci/jenkins#26880 * jenkinsci/jenkins#26690 * jenkinsci/jenkins#26668 * jenkinsci/jenkins#26600 * jenkinsci/jenkins#26587 * jenkinsci/jenkins#11216 * jenkinsci/jenkins#10432 * jenkinsci/jenkins#8559
Testing pull requests: * jenkinsci#27223 * jenkinsci#27222 * jenkinsci#27218 * jenkinsci#27217 * jenkinsci#27201 * jenkinsci#27159 * jenkinsci#27141 * jenkinsci#27088 * jenkinsci#27083 * jenkinsci#27067 * jenkinsci#27065 * jenkinsci#27063 * jenkinsci#27032 * jenkinsci#27031 * jenkinsci#26923 * jenkinsci#26922 * jenkinsci#26913 * jenkinsci#26880 * jenkinsci#26690 * jenkinsci#26668 * jenkinsci#26600 * jenkinsci#25920 * jenkinsci#11216
Fixes #27188
ArtifactArchiverpreviously reduced anAccessDeniedExceptionto its raw exception string. Although this identified the denied path, it did not explain which archive operation failed, identify the workspace, or tell administrators which permissions to check.For non-empty unreadable files,
TarArchiverpreviously wrote the tar header before opening the file. When opening failed, closing the incomplete entry replaced the originalAccessDeniedExceptionwith tar and EOF errors. This change opens regular files before writing their tar headers, preserving the actionable access-denied failure without leaving an incomplete entry.ArtifactArchivernow reports the denied path and workspace and directs administrators to verify the controller and agent file permissions. It deliberately preserves the existing fail-safe behavior: the build still fails rather than reporting an incomplete artifact set as successfully archived. The detailed exception remains available in thehudson.tasks.ArtifactArchiverlogger atFINElevel, while the build log remains concise.The existing Unix-only regression test now covers the actionable message and is associated with both #22902 and #27188.
Testing done
Ran the focused regression test through the complete Maven reactor, including the Jenkins WAR and a Unix agent:
Result:
Also ran the dedicated tar archiver tests:
The test verifies that an unreadable matched artifact:
FAILURE;Screenshots (UI changes only)
Not applicable; this changes build-log diagnostics only.
Before
Not applicable.
After
Not applicable.
Proposed changelog entries
Proposed changelog category
/label bug
Proposed upgrade guidelines
N/A
Submitter checklist
@Restrictedor have@since TODOJavadocs, as appropriate. No public API was added.@Deprecated(since = "TODO")or@Deprecated(forRemoval = true, since = "TODO"), if applicable. No deprecations were added.Desired reviewers
N/A
Before the changes are marked as
ready-for-merge:Maintainer checklist
upgrade-guide-neededlabel is set and the Proposed upgrade guidelines section is complete.lts-candidatefor consideration.