diff --git a/src/services/Elastic.Changelog/Evaluation/ChangelogPrepareArtifactService.cs b/src/services/Elastic.Changelog/Evaluation/ChangelogPrepareArtifactService.cs index a7b768073e..4308bf1e2c 100644 --- a/src/services/Elastic.Changelog/Evaluation/ChangelogPrepareArtifactService.cs +++ b/src/services/Elastic.Changelog/Evaluation/ChangelogPrepareArtifactService.cs @@ -47,11 +47,17 @@ public async Task PrepareArtifact(IDiagnosticsCollector collector, Prepare if (sourceYaml != null) { - changelogFilename = input.ExistingChangelogFilename != null - ? _fileSystem.Path.GetFileName(input.ExistingChangelogFilename) + // Treat empty as unset: CLI parsers (e.g. Argh) forward `--flag ""` + // as the empty string instead of null. An empty filename here would + // make Path.Combine(OutputDir, "") collapse to OutputDir itself and + // turn the artifact write into a write against the directory path, + // which fails with EACCES on Linux. + var hasExistingFilename = !string.IsNullOrEmpty(input.ExistingChangelogFilename); + changelogFilename = hasExistingFilename + ? _fileSystem.Path.GetFileName(input.ExistingChangelogFilename!) : _fileSystem.Path.GetFileName(sourceYaml); - if (input.ExistingChangelogFilename != null) + if (hasExistingFilename) _logger.LogInformation("Reusing existing filename {Filename} for stable path on branch", changelogFilename); var destYaml = _fileSystem.Path.Combine(input.OutputDir, changelogFilename); diff --git a/tests/Elastic.Changelog.Tests/Evaluation/ChangelogPrepareArtifactServiceTests.cs b/tests/Elastic.Changelog.Tests/Evaluation/ChangelogPrepareArtifactServiceTests.cs index 6e8450bba0..b084a2208a 100644 --- a/tests/Elastic.Changelog.Tests/Evaluation/ChangelogPrepareArtifactServiceTests.cs +++ b/tests/Elastic.Changelog.Tests/Evaluation/ChangelogPrepareArtifactServiceTests.cs @@ -232,6 +232,26 @@ public async Task PrepareArtifact_ExistingFilename_RenamesStagingFileToMatch() metadata.ChangelogFilename.Should().Be("1735689600-old-title.yaml"); } + [Fact] + public async Task PrepareArtifact_EmptyExistingFilename_FallsBackToStagingFilename() + { + // Regression: CLI parsers (Argh) forward `--existing-changelog-filename ""` + // as the empty string instead of null. An empty filename used to make + // Path.Combine(OutputDir, "") collapse to OutputDir itself and write the + // artifact YAML at the directory path → EACCES on Linux. + await SetupStagingYaml("1735700000-new-title.yaml"); + await SetupConfig(); + var service = CreateService(); + var args = DefaultArgs() with { ExistingChangelogFilename = string.Empty }; + + var result = await service.PrepareArtifact(Collector, args, CancellationToken.None); + + result.Should().BeTrue(); + FileSystem.File.Exists(Path.Join(OutputDir, "1735700000-new-title.yaml")).Should().BeTrue(); + var metadata = ReadMetadata(); + metadata.ChangelogFilename.Should().Be("1735700000-new-title.yaml"); + } + [Fact] public async Task PrepareArtifact_MissingStagingYaml_StatusError() {