Skip to content

fix: surface nested scratchpad files and name published artifacts - #166

Merged
gavin-jeong merged 1 commit into
masterfrom
scratchpad-recursive-listing
Sep 16, 2026
Merged

gavin-jeong merged 1 commit into
masterfrom
scratchpad-recursive-listing

Conversation

@gavin-jeong

Copy link
Copy Markdown
Collaborator

What

A session's scratchpad listing read only the top level, so every file an agent
organized into a subdirectory was invisible. A session whose own summary pointed
at scratchpad/kp/pr/pr.md showed one unrelated file in the preview — the
listing looked complete while omitting the work.

Measured on that session before the change:

scratchpad files surfaced: 1
   reply.txt 4312

kp/pr/issue.md and kp/pr/pr.md exist on disk but were never scanned:
LoadScratchpadFiles walked one os.ReadDir and continued on every directory.

After:

kp/bin/setup-envtest (10534146)
kp/karpenter/        (git repository)
kp/kp-main/          (git repository)
kp/pr/issue.md       (4087)
kp/pr/pr.md          (3772)
reply.txt            (4312)

Artifacts had the opposite problem. The URLs were tracked correctly — across the
whole transcript corpus, every Published-marked artifact URL was extracted —
but the row read artifact:6c6ced7d, naming nothing. SessionRef.Title and the
renderer that prefers it already existed; nothing filled it.

artifact:6c6ced7d  →  mobile-audit-verdict.html
artifact:653a44a4  →  seven-axes-ko.html
artifact:ba162635  →  cell-iam-map.html

How

Recursion is the easy half. Measuring it surfaced two problems that the naive
version got wrong, and both are why this touches more than one line:

A cloned repo swallows the listing. The first version let a kp/karpenter
checkout fill all 300 slots, pushing the session's own pr/issue.md out
entirely — strictly worse than the bug being fixed. A git work tree is upstream
code, not session output, so it is listed as one row and never descended into.

The walk blocks the UI thread. A 70k-entry scratchpad took ~1.2s to walk
fully. This runs on the UI thread, so that is the freeze #161 fixed, reintroduced
by a different path. A file cap cannot bound it — the cost is in traversing
entries, paid before any file is selected — so the walk carries its own visit
budget. 3.2s → 346ms on the worst scratchpad measured.

Two smaller ordering decisions follow from the same measurements:

  • The file cap trims by mtime, not walk order. A walk is alphabetical, so
    trimming mid-walk keeps whatever sorts first rather than whatever the session
    last touched.
  • Bodies are read after trimming, so the byte budget is spent on files that
    survived rather than on whatever the walk reached first.

When any bound bites, the listing appends a truncation row. A silently short list
reads as "this is everything", which turns a missing file into apparent proof it
never existed.

For artifacts, the Published <path> at <url> tool-result line carries the
source path; its basename becomes the Title. One line can announce several
publishes, so names are paired to their own URL — mislabeling an artifact is
worse than leaving it unnamed.

Test

go vet ./... clean, go test ./... green across all packages.

Six tests added, each pinning one of the failure modes above rather than the
happy path: recursion, cloned-repo containment, dependency-dir skipping,
truncation marking, artifact title extraction, and multi-publish URL pairing. All
isolate HOME with t.Setenv("HOME", t.TempDir()).

Verified against real transcripts and scratchpads on disk, not only fixtures —
that is where the cloned-repo and walk-cost problems showed up, neither of which
a fixture would have exposed.

Note

internal/tui/splitpane.go has a pre-existing gofmt violation. Unrelated to this
change, so left alone.

A session's scratchpad listing read only the top level, so every file an
agent organized into a subdirectory was invisible. A session whose own
summary pointed at "scratchpad/kp/pr/pr.md" showed one unrelated file in
the preview — the listing looked complete while omitting the work.

The walk is now recursive, bounded three ways so it stays on the UI
thread:

  * A git checkout in the scratchpad is listed as one row, not walked.
    One cloned karpenter tree otherwise filled every listing slot and
    pushed the session's own pr/issue.md out entirely.
  * The file cap trims by mtime, not walk order — a walk is alphabetical,
    so trimming mid-walk keeps whatever sorts first rather than whatever
    the session last touched. Bodies are read after trimming, so the byte
    budget is spent on files that survived.
  * A visit budget bounds the walk itself, which the file cap cannot: the
    cost is in traversing entries, paid before any file is selected. One
    measured scratchpad holds 70k entries and took ~1.2s to walk fully —
    long enough to freeze the preview. It now costs ~136ms.

When a bound bites, the listing says so rather than silently ending
short, so a missing file is not mistaken for proof it never existed.

Artifacts had the opposite problem: the URL was tracked but the row read
"artifact:6c6ced7d", naming nothing. The Artifact tool's result line
carries the source path, so its basename becomes the ref Title, which the
renderer already preferred. Multiple publishes on one line are paired by
URL — mislabeling an artifact is worse than leaving it unnamed.
@upwind-code-us

upwind-code-us Bot commented Sep 15, 2026

Copy link
Copy Markdown

Upwind Upwind Code Scan - ⚠️ Warn

1 newly introduced vulnerabilities · 0 resolved · 1 total in this PR vs master

Total breakdown: 🔶 1 High


🔶 High · 1 finding
CVE Package Version Fix
CVE-2026-56852 golang.org/x/text v0.3.8 0.39.0

View full analysis in Upwind Console

Scan completed in 9s

Scan history (1 scan)
Commit Scanned at New Resolved Net
56d15e5 < 2026-09-15 08:50 UTC +1 0 +1

Last scanned: 56d15e5 · 2026-09-15 08:50 UTC

@upwind-code-us

upwind-code-us Bot commented Sep 15, 2026

Copy link
Copy Markdown

Upwind Upwind IaC Scan - ✅ Passed

0 newly introduced misconfigurations · 0 resolved · 0 total in this PR vs the default branch

View full analysis in Upwind Console →

Scan completed in 3s

Scan history (2 scans)
Commit Scanned at New Resolved Net
56d15e5 2026-09-15 08:51 UTC
56d15e5 < 2026-09-15 08:51 UTC 0 0 0

Last scanned: 56d15e5 · 2026-09-15 08:51 UTC

@Kairo-Kim Kairo-Kim added the auto-review/approved Auto-approved by the Slack auto-reviewer bot label Sep 15, 2026

@jinsekim jinsekim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@gavin-jeong
gavin-jeong merged commit 29c4522 into master Sep 16, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-review/approved Auto-approved by the Slack auto-reviewer bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants