feat(hooks): surface fsync-skip warnings to AI agent via tool output
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
|
||||
import type { FsyncSkipEntry } from "./fsync-skip-tracker"
|
||||
import { formatFsyncSkipWarning } from "./fsync-skip-warning-formatter"
|
||||
|
||||
function makeEntry(index: number, classification: FsyncSkipEntry["pathClassification"]): FsyncSkipEntry {
|
||||
return {
|
||||
filePath: `/path/${index}`,
|
||||
contextLabel: `atomicWrite:/path/${index}`,
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification: classification,
|
||||
timestamp: 1000 + index,
|
||||
}
|
||||
}
|
||||
|
||||
describe("formatFsyncSkipWarning", () => {
|
||||
it("returns empty string for zero entries", () => {
|
||||
expect(formatFsyncSkipWarning([])).toBe("")
|
||||
})
|
||||
|
||||
it("includes iCloud environment, path, and code for one entry", () => {
|
||||
const warning = formatFsyncSkipWarning([makeEntry(1, "icloud")])
|
||||
expect(warning).toContain("iCloud Drive")
|
||||
expect(warning).toContain("/path/1")
|
||||
expect(warning).toContain("EPERM")
|
||||
})
|
||||
|
||||
it("shows all five paths when exactly five entries exist", () => {
|
||||
const warning = formatFsyncSkipWarning([
|
||||
makeEntry(1, "icloud"),
|
||||
makeEntry(2, "icloud"),
|
||||
makeEntry(3, "icloud"),
|
||||
makeEntry(4, "icloud"),
|
||||
makeEntry(5, "icloud"),
|
||||
])
|
||||
|
||||
expect(warning).toContain("/path/1")
|
||||
expect(warning).toContain("/path/5")
|
||||
expect(warning).not.toContain("and 1 more")
|
||||
})
|
||||
|
||||
it("shows five paths plus overflow summary when six entries exist", () => {
|
||||
const warning = formatFsyncSkipWarning([
|
||||
makeEntry(1, "icloud"),
|
||||
makeEntry(2, "icloud"),
|
||||
makeEntry(3, "icloud"),
|
||||
makeEntry(4, "icloud"),
|
||||
makeEntry(5, "icloud"),
|
||||
makeEntry(6, "icloud"),
|
||||
])
|
||||
|
||||
expect(warning).toContain("/path/5")
|
||||
expect(warning).not.toContain("/path/6")
|
||||
expect(warning).toContain("... and 1 more")
|
||||
})
|
||||
|
||||
it("uses the most common classification when entries are mixed", () => {
|
||||
const warning = formatFsyncSkipWarning([
|
||||
makeEntry(1, "onedrive"),
|
||||
makeEntry(2, "onedrive"),
|
||||
makeEntry(3, "icloud"),
|
||||
])
|
||||
|
||||
expect(warning).toContain("Detected environment: OneDrive")
|
||||
})
|
||||
|
||||
it("matches required section format", () => {
|
||||
const warning = formatFsyncSkipWarning([makeEntry(1, "unknown")])
|
||||
|
||||
expect(warning).toContain("[fsync-skipped] 1 write(s) bypassed fsync")
|
||||
expect(warning).toContain("Affected paths:")
|
||||
expect(warning).toContain("What this means:")
|
||||
expect(warning).toContain("The write+rename succeeded")
|
||||
expect(warning).not.toContain("Detected environment:")
|
||||
expect(warning).toContain("filesystem does not support fsync")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describePathClassification } from "./classify-path-environment"
|
||||
import type { FsyncSkipEntry } from "./fsync-skip-tracker"
|
||||
|
||||
const MAX_PATH_LINES = 5
|
||||
|
||||
function selectMostCommonClassification(
|
||||
entries: FsyncSkipEntry[],
|
||||
): FsyncSkipEntry["pathClassification"] {
|
||||
const counts = new Map<FsyncSkipEntry["pathClassification"], number>()
|
||||
|
||||
for (const entry of entries) {
|
||||
const currentCount = counts.get(entry.pathClassification) ?? 0
|
||||
counts.set(entry.pathClassification, currentCount + 1)
|
||||
}
|
||||
|
||||
let selected: FsyncSkipEntry["pathClassification"] = "unknown"
|
||||
let selectedCount = -1
|
||||
for (const [classification, count] of counts.entries()) {
|
||||
if (count > selectedCount) {
|
||||
selected = classification
|
||||
selectedCount = count
|
||||
}
|
||||
}
|
||||
|
||||
return selected
|
||||
}
|
||||
|
||||
export function formatFsyncSkipWarning(entries: FsyncSkipEntry[]): string {
|
||||
if (entries.length === 0) return ""
|
||||
|
||||
const selectedClassification = selectMostCommonClassification(entries)
|
||||
const selectedDescription = describePathClassification(selectedClassification)
|
||||
const shownEntries = entries.slice(0, MAX_PATH_LINES)
|
||||
const hiddenCount = Math.max(entries.length - shownEntries.length, 0)
|
||||
const pathLines = shownEntries.map((entry) => ` - ${entry.filePath} (code: ${entry.errorCode})`)
|
||||
if (hiddenCount > 0) {
|
||||
pathLines.push(` ... and ${hiddenCount} more`)
|
||||
}
|
||||
|
||||
const environmentLines = selectedClassification === "unknown"
|
||||
? []
|
||||
: [`Detected environment: ${selectedDescription}`]
|
||||
|
||||
const durabilityLine = selectedClassification === "unknown"
|
||||
? " - Crash durability is best-effort because this filesystem does not support fsync."
|
||||
: " - Crash durability is best-effort on this filesystem (this is normal for iCloud, OneDrive, network drives, antivirus-locked paths)."
|
||||
|
||||
return [
|
||||
"---",
|
||||
`[fsync-skipped] ${entries.length} write(s) bypassed fsync because the underlying filesystem rejected the syscall.`,
|
||||
"",
|
||||
...environmentLines,
|
||||
"Affected paths:",
|
||||
...pathLines,
|
||||
"",
|
||||
"What this means:",
|
||||
" - The write+rename succeeded — the file is on disk, atomicity is preserved.",
|
||||
durabilityLine,
|
||||
" - No action required. Operation completed successfully.",
|
||||
].join("\n")
|
||||
}
|
||||
Reference in New Issue
Block a user