diff --git a/src/Elastic.Documentation.Tooling/FileSystems/ChangelogFileSystem.cs b/src/Elastic.Documentation.Tooling/FileSystems/ChangelogFileSystem.cs
index 266d91f06..db4abf0c7 100644
--- a/src/Elastic.Documentation.Tooling/FileSystems/ChangelogFileSystem.cs
+++ b/src/Elastic.Documentation.Tooling/FileSystems/ChangelogFileSystem.cs
@@ -10,13 +10,14 @@ namespace Elastic.Documentation.FileSystems;
///
/// Scope for changelog commands: the git root of the target repository.
-/// Allows reading .git metadata (remote URL, branch); does not include
-/// AppData or build artifacts — changelog operates only within the repo working tree.
+/// Allows reading .git metadata (remote URL, branch) and writing under
+/// the conventional .artifacts CI staging directory. Does not include
+/// AppData — changelog operates only within the repo working tree.
///
public class ChangelogFileSystem(IDirectoryInfo root, IFileSystem? inner = null)
: ScopedFileSystem(inner ?? Physical, new ScopedFileSystemOptions([root.FullName])
{
- AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git" },
+ AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".artifacts" },
AllowedHiddenFileNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git" }
}),
IChangelogFileSystem
diff --git a/tests/Elastic.Changelog.Tests/Evaluation/ChangelogCiFileSystemTests.cs b/tests/Elastic.Changelog.Tests/Evaluation/ChangelogCiFileSystemTests.cs
new file mode 100644
index 000000000..6ba926d37
--- /dev/null
+++ b/tests/Elastic.Changelog.Tests/Evaluation/ChangelogCiFileSystemTests.cs
@@ -0,0 +1,70 @@
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
+using System.IO.Abstractions.TestingHelpers;
+using AwesomeAssertions;
+using Elastic.Documentation.Configuration;
+using Elastic.Documentation.FileSystems;
+using Nullean.ScopedFileSystem;
+
+namespace Elastic.Changelog.Tests.Evaluation;
+
+///
+/// Regression: changelog CI staging uses .artifacts/changelog-* under the checkout.
+/// ScopedFileSystem rejects hidden path segments unless they are allowlisted.
+///
+public class ChangelogCiFileSystemTests
+{
+ private static readonly string Root = Paths.WorkingDirectoryRoot.FullName;
+
+ [Fact]
+ public void ChangelogFileSystem_AllowsArtifactsStagingAndArtifactDirs()
+ {
+ var mock = new MockFileSystem(new MockFileSystemOptions { CurrentDirectory = Root });
+ var fs = ChangelogFileSystem.FromWorkingDirectory(mock);
+
+ var staging = Path.Join(Root, ".artifacts", "changelog-staging");
+ var artifact = Path.Join(Root, ".artifacts", "changelog-artifact");
+ var stagingFile = Path.Join(staging, "42.yaml");
+ var artifactFile = Path.Join(artifact, "metadata.json");
+
+ var create = () =>
+ {
+ fs.Directory.CreateDirectory(staging);
+ fs.Directory.CreateDirectory(artifact);
+ fs.File.WriteAllText(stagingFile, "title: test");
+ fs.File.WriteAllText(artifactFile, "{}");
+ };
+
+ create.Should().NotThrow();
+ fs.File.Exists(stagingFile).Should().BeTrue();
+ fs.File.Exists(artifactFile).Should().BeTrue();
+ }
+
+ [Fact]
+ public void ChangelogFileSystem_BlocksOtherHiddenDirectories()
+ {
+ var mock = new MockFileSystem(new MockFileSystemOptions { CurrentDirectory = Root });
+ var fs = ChangelogFileSystem.FromWorkingDirectory(mock);
+ var hidden = Path.Join(Root, ".hidden", "nested");
+
+ var create = () => fs.Directory.CreateDirectory(hidden);
+
+ create.Should().Throw()
+ .WithMessage("*hidden*");
+ }
+
+ [Fact]
+ public void RunnerTempFileSystem_BlocksOtherHiddenDirectories()
+ {
+ var mock = new MockFileSystem(new MockFileSystemOptions { CurrentDirectory = Root });
+ var fs = new RunnerTempFileSystem(mock.DirectoryInfo.New(Root), inner: mock);
+ var hidden = Path.Join(Root, ".hidden", "nested");
+
+ var create = () => fs.Directory.CreateDirectory(hidden);
+
+ create.Should().Throw()
+ .WithMessage("*hidden*");
+ }
+}