Skip to content

Improve diagnostics for unreadable artifacts - #27201

Open
Hardik180704 wants to merge 5 commits into
jenkinsci:masterfrom
Hardik180704:fix/issue-27188-unreadable-artifacts
Open

Improve diagnostics for unreadable artifacts#27201
Hardik180704 wants to merge 5 commits into
jenkinsci:masterfrom
Hardik180704:fix/issue-27188-unreadable-artifacts

Conversation

@Hardik180704

@Hardik180704 Hardik180704 commented Aug 6, 2026

Copy link
Copy Markdown

Fixes #27188

ArtifactArchiver previously reduced an AccessDeniedException to 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, TarArchiver previously wrote the tar header before opening the file. When opening failed, closing the incomplete entry replaced the original AccessDeniedException with 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.

ArtifactArchiver now 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 the hudson.tasks.ArtifactArchiver logger at FINE level, 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:

mvn -ntp -pl test -am -Dtest=hudson.tasks.ArtifactArchiverTest#archiveNotReadable -Dskip.frontend=true -Dyarn.lint.skip=true package

Result:

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

Also ran the dedicated tar archiver tests:

mvn -ntp -pl core -am -Dtest=hudson.util.io.TarArchiverTest -Dskip.frontend=true -Dyarn.lint.skip=true package
Tests run: 4, Failures: 0, Errors: 0, Skipped: 1
BUILD SUCCESS

The test verifies that an unreadable matched artifact:

  • keeps the build result as FAILURE;
  • logs the denied path and workspace with permission guidance;
  • avoids the incomplete tar-entry and EOF stack traces reproduced with a non-empty file;
  • does not print a long stack trace.

Screenshots (UI changes only)

Not applicable; this changes build-log diagnostics only.

Before

Not applicable.

After

Not applicable.

Proposed changelog entries

  • Improve artifact archiving diagnostics when file access is denied.

Proposed changelog category

/label bug

Proposed upgrade guidelines

N/A

Submitter checklist

  • The issue, if it exists, is well-described.
  • The changelog entries and upgrade guidelines are appropriate for the audience affected by the change (users or developers, depending on the change) and are in the imperative mood (see examples in the Jenkins changelog).
  • There is automated testing or an explanation as to why this change has no tests.
  • New public classes, fields, and methods are annotated with @Restricted or have @since TODO Javadocs, as appropriate. No public API was added.
  • New deprecations are annotated with @Deprecated(since = "TODO") or @Deprecated(forRemoval = true, since = "TODO"), if applicable. No deprecations were added.
  • UI changes do not introduce CSP regressions. No UI or JavaScript changes were made.
  • Dependency updates include links to external changelogs and full differentials where possible. No dependencies were updated.
  • New APIs and extension points link to at least one consumer. No APIs or extension points were added.

Desired reviewers

N/A

Before the changes are marked as ready-for-merge:

Maintainer checklist

  • There are at least two (2) approvals for the pull request and no outstanding requests for change.
  • Conversations in the pull request are over, or it is explicit that a reviewer is not blocking the change.
  • Changelog entries in the pull request title and/or Proposed changelog entries are accurate, human-readable, and in the imperative mood.
  • Proper changelog labels are set so that the changelog can be generated automatically.
  • If the change needs additional upgrade steps for users, the upgrade-guide-needed label is set and the Proposed upgrade guidelines section is complete.
  • If it would make sense to backport the change to LTS, the issue or pull request is labeled lts-candidate for consideration.

Copilot AI lite review requested due to automatic review settings August 6, 2026 06:04
@comment-ops-bot comment-ops-bot Bot added the bug For changelog: Minor bug. Will be listed after features label Aug 6, 2026

Copilot AI 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.

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 ArtifactArchiver to throw an AbortException with the new message and log details at FINE.
  • 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();
Copilot AI review requested due to automatic review settings August 6, 2026 06:13

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@MarkEWaite MarkEWaite 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.

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:

  1. Download and unzip gh-core-27188.zip on my Linux computer
  2. Change to the gh-core-27188 directory and run bash ./README
  3. Open a web browser on port 8080 of the Linux computer
  4. Login with my Linux username ('mwaite') and the password 'p-' plus my username ('p-mwaite')
  5. 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'
            }
        }
    }
}

Copilot AI review requested due to automatic review settings August 6, 2026 13:29

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@Hardik180704

Copy link
Copy Markdown
Author

@MarkEWaite
Thanks for the detailed reproduction, Mark. You were right—the existing test used a zero-byte unreadable file, so it did not expose the failure shown in your environment.
I updated the test to use a non-empty unreadable file and reproduced the same incomplete tar-entry and EOF stack traces. The root cause was that TarArchiver wrote the tar header before opening the file, allowing the original AccessDeniedException to be masked when the incomplete entry was closed.
Commit be054f7 now opens regular files before writing their tar headers, preserving the original access-denied failure. I verified it with the strengthened Unix regression test and the dedicated TarArchiverTest suite; both pass successfully.
Thanks again for providing the test environment and identifying the gap in our original reproduction.

@MarkEWaite MarkEWaite 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.

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.

Copilot AI review requested due to automatic review settings August 6, 2026 18:57
@Hardik180704

Copy link
Copy Markdown
Author

@MarkEWaite
Thanks for pointing this out. I added a direct in-process regression test, TarArchiverTest.unreadableFileDoesNotLeaveIncompleteEntry, which invokes TarArchiver.visit with a non-empty unreadable file and verifies that the original AccessDeniedException is preserved without leaving an incomplete tar entry.

I also kept the existing ArtifactArchiverTest for end-to-end coverage on a Jenkins agent. Both focused test suites pass successfully.

@MarkEWaite

Copy link
Copy Markdown
Contributor

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?

@Hardik180704

Copy link
Copy Markdown
Author

@MarkEWaite
The TarArchiver change is needed because, for a non-empty unreadable file, the original AccessDeniedException does not reach ArtifactArchiver with the previous implementation.

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.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@MarkEWaite

Copy link
Copy Markdown
Contributor

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.

@Hardik180704

Copy link
Copy Markdown
Author

@MarkEWaite
Thanks for confirming that the TarArchiver changes are executed on the agent. Agreed that running the plugin compatibility and acceptance test harness suites is the appropriate next step. Please let me know if any failures require changes from my side.

@MarkEWaite MarkEWaite 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.

2 changes for the issue references.

Comment thread core/src/test/java/hudson/util/io/TarArchiverTest.java Outdated
Comment thread test/src/test/java/hudson/tasks/ArtifactArchiverTest.java Outdated
Copilot AI review requested due to automatic review settings August 6, 2026 20:53
@Hardik180704

Copy link
Copy Markdown
Author

@MarkEWaite
Thanks for the correction. I updated both @Issue annotations to use the full GitHub URL for issue 27188 while retaining the Jira reference for JENKINS-21905.

I also reran TarArchiverTest and ArtifactArchiverTest#archiveNotReadable; both pass successfully.

@Hardik180704
Hardik180704 requested a review from MarkEWaite August 6, 2026 21:04
@MarkEWaite MarkEWaite added needs-ath-build Needs to run through the full acceptance-test-harness suite needs-pct-build A run through of bom is needed labels Aug 6, 2026

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@MarkEWaite MarkEWaite mentioned this pull request Aug 7, 2026
14 tasks
@MarkEWaite MarkEWaite added pct-successful This PR has successfully passed the full plugin-compatibility-test suite and removed needs-pct-build A run through of bom is needed labels Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug For changelog: Minor bug. Will be listed after features needs-ath-build Needs to run through the full acceptance-test-harness suite pct-successful This PR has successfully passed the full plugin-compatibility-test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ArchiveArtifacts fails if one of the file being archived does not have the read attribute. Unix only.

3 participants