Repository navigation
fix(agents): give agent output a well-known home and stop lock markers leaking into new worktrees #423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(agents): give agent output a well-known home and stop lock markers leaking into new worktrees #423
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # Regression tests for the agent evidence sweep in WorktreeCleanup.ps1. Needs only PowerShell 7. | ||
| $ErrorActionPreference = 'Stop' | ||
| . (Join-Path $PSScriptRoot 'WorktreeCleanup.ps1') | ||
| $tempBase = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()) | ||
| $testRoot = [IO.Path]::GetFullPath((Join-Path $tempBase "evidence-cleanup-$([Guid]::NewGuid().ToString('N'))")) | ||
| if (-not $testRoot.StartsWith($tempBase, [StringComparison]::OrdinalIgnoreCase)) { throw 'Unsafe fixture path.' } | ||
|
|
||
| function Assert-True([bool]$Condition, [string]$Message) { | ||
| if (-not $Condition) { throw $Message } | ||
| } | ||
|
|
||
| function New-Evidence([string]$Path, [int]$AgeDays) { | ||
| New-Item -ItemType Directory -Path (Split-Path $Path -Parent) -Force | Out-Null | ||
| Set-Content -LiteralPath $Path -Value 'evidence' | ||
| (Get-Item -LiteralPath $Path).LastWriteTimeUtc = [DateTime]::UtcNow.AddDays(-$AgeDays) | ||
| } | ||
|
|
||
| try { | ||
| $root = Get-AgentEvidenceRoot -MainCheckout (Join-Path $testRoot 'Repo') | ||
| Assert-True ($root -ceq (($testRoot -replace '\\', '/') + '/Repo-evidence')) "Unexpected evidence root: $root" | ||
|
|
||
| New-Evidence (Join-Path $root 'issue-1/build.log') 10 | ||
| New-Evidence (Join-Path $root 'issue-2/old.log') 10 | ||
| New-Evidence (Join-Path $root 'issue-2/nested/fresh.log') 1 | ||
| New-Evidence (Join-Path $root 'pr-3-body.md') 10 | ||
| New-Evidence (Join-Path $root 'pr-4-body.md') 1 | ||
|
Comment on lines
+22
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new cleanup test never creates a link, so it cannot catch changes that break the sweep's link safeguards. Add a top-level linked entry and a stale folder containing a junction or symlink to an outside directory. Assert that the linked entry is skipped and that cleanup leaves the outside files unchanged. Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| foreach ($directory in Get-ChildItem -LiteralPath $root -Directory -Recurse) { | ||
| $directory.LastWriteTimeUtc = [DateTime]::UtcNow.AddDays(-10) | ||
| } | ||
|
|
||
| $wouldRemove = Remove-StaleAgentEvidence -Root $root -RetentionDays 7 -WhatIf | ||
| Assert-True ($wouldRemove -eq 2) "Dry run counted $wouldRemove stale entries." | ||
| Assert-True (Test-Path -LiteralPath (Join-Path $root 'issue-1')) 'Dry run removed evidence.' | ||
|
|
||
| $removed = Remove-StaleAgentEvidence -Root $root -RetentionDays 7 | ||
| Assert-True ($removed -eq 2) "Removed $removed stale entries." | ||
| Assert-True (-not (Test-Path -LiteralPath (Join-Path $root 'issue-1'))) 'Stale evidence folder survived.' | ||
| Assert-True (-not (Test-Path -LiteralPath (Join-Path $root 'pr-3-body.md'))) 'Stale evidence file survived.' | ||
| Assert-True (Test-Path -LiteralPath (Join-Path $root 'issue-2/old.log')) 'Folder with recent writes was removed.' | ||
| Assert-True (Test-Path -LiteralPath (Join-Path $root 'pr-4-body.md')) 'Recent evidence file was removed.' | ||
|
|
||
| Assert-True ((Remove-StaleAgentEvidence -Root (Join-Path $testRoot 'missing') -RetentionDays 7) -eq 0) 'Missing root reported removals.' | ||
| Write-Host 'OK agent evidence cleanup tests passed.' | ||
| } | ||
| finally { | ||
| if (Test-Path -LiteralPath $testRoot) { Remove-Item -LiteralPath $testRoot -Recurse -Force } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,6 +99,58 @@ function Remove-WorktreeDirectory { | |
| return -not (Test-Path -LiteralPath $Worktree) | ||
| } | ||
|
|
||
| # AGENT EVIDENCE | ||
| # Agents keep logs, reports and PR-body drafts for a work item under | ||
| # <main checkout>-evidence/<lock name>/, beside the worktree root, so nothing lands | ||
| # loose in the parent directory. Evidence matters only while its item is active. | ||
| function Get-AgentEvidenceRoot { | ||
| [CmdletBinding()] | ||
| param([Parameter(Mandatory)][string]$MainCheckout) | ||
|
|
||
| return "$(($MainCheckout -replace '\\', '/').TrimEnd('/'))-evidence" | ||
| } | ||
|
|
||
| # Removes each top-level entry of the evidence root whose newest write is older than | ||
| # the retention window. Returns the number of entries removed (or that would be). | ||
| function Remove-StaleAgentEvidence { | ||
| [CmdletBinding()] | ||
| param( | ||
| [Parameter(Mandatory)][string]$Root, | ||
| [Parameter(Mandatory)][int]$RetentionDays, | ||
| [switch]$WhatIf | ||
| ) | ||
|
|
||
| if (-not (Test-Path -LiteralPath $Root -PathType Container)) { return 0 } | ||
| $cutoff = [DateTime]::UtcNow.AddDays(-$RetentionDays) | ||
| $removed = 0 | ||
| foreach ($entry in (Get-ChildItem -LiteralPath $Root -Force -ErrorAction SilentlyContinue)) { | ||
| if (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { | ||
| Write-Host "sweep: skipping evidence entry that is a link: $($entry.FullName)" | ||
| continue | ||
| } | ||
| $items = @($entry) | ||
| if ($entry.PSIsContainer) { | ||
| $items += @(Get-ChildItem -LiteralPath $entry.FullName -Recurse -Force -ErrorAction SilentlyContinue) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The age scan suppresses errors and treats the files it could read as the complete entry. If a nested directory cannot be listed, its recent files are omitted. The sweep can then delete readable files from an entry that should be kept. Skip the entry and log a warning whenever its age scan is incomplete. |
||
| } | ||
| $newest = ($items | Measure-Object -Property LastWriteTimeUtc -Maximum).Maximum | ||
| if ($newest -ge $cutoff) { continue } | ||
| if ($WhatIf) { | ||
| Write-Host "sweep: WOULD remove stale evidence $($entry.FullName)" | ||
| $removed++ | ||
| continue | ||
| } | ||
| if ($entry.PSIsContainer -and -not (Remove-WorktreeDirectoryLinks -Worktree $entry.FullName -Label '(evidence)')) { continue } | ||
| Remove-Item -LiteralPath $entry.FullName -Recurse -Force -ErrorAction SilentlyContinue | ||
| if (Test-Path -LiteralPath $entry.FullName) { | ||
| Write-Host "sweep: WARNING could not remove stale evidence $($entry.FullName)" | ||
| continue | ||
| } | ||
| Write-Host "sweep: removed stale evidence $($entry.FullName)" | ||
| $removed++ | ||
| } | ||
| return $removed | ||
| } | ||
|
|
||
| # PENDING REMOVALS | ||
| # On Windows a directory cannot be deleted while any process has its current directory | ||
| # inside it, for example a shell that ran Merge-Pr.ps1 from the worktree, and build or | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Get-WorktreeLockMarkercompares paths with-ne, which ignores case even on Linux. If/work/taskinherits a marker from/work/Task, this check accepts it despite those being separate directories. Implicitstatus,renew, orreleasethen targets the first worktree's lock. Compare paths case-sensitively outside Windows, asTest-SameNativePathalready does.