Skip to content

Day 31 backlog: Go 1.26.8, backup pruning, three-runner smoke, and one honest permission answer - #1

Merged
packetloss404 merged 6 commits into
mainfrom
chore/day-31-backlog
Sep 5, 2026
Merged

Day 31 backlog: Go 1.26.8, backup pruning, three-runner smoke, and one honest permission answer#1
packetloss404 merged 6 commits into
mainfrom
chore/day-31-backlog

Conversation

@packetloss404

@packetloss404 packetloss404 commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Four of the eight day-31 items from the security audit, one commit each so any of them can be reverted alone. Three items are deliberately left out and are explained below.

This PR adds three CI jobs that pass and introduces no new failures. CI is nonetheless red, because it was already red on main. The attribution is at the bottom, job by job, with evidence.

What is in

K-01 — pin Go 1.26.8 (6e8e778)

ci.yml and release.yml both read one GO_VERSION key, still pinned to 1.26.3 from the initial workflow. The backlog entry named 1.26.6; 1.26.8 was the current 1.26 patch release when this was written, so it is the pin.

CI measured the effect. On main at 1.26.3, govulncheck reports 15 reachable vulnerabilities, 8 of them in the Go standard library. On this branch at 1.26.8 it reports 7, none in the standard library. So the pin cleared 8 reachable stdlib advisories, not the 9 the audit predicted, with no code change.

K-06 — collect_agent_results stops claiming a gate it never had (fe9d3f5)

RequiresApproval() returned true for the foreground tool. Every built-in profile lists collect_agent_results in permissions.readOnlyTool, and that branch returns allow before RequiresApproval is ever consulted, so the prompt never happened. Two sources described the tool and they disagreed; docs/advanced-guide.md already documents collection as automatic, with an explicit session rule as the way to re-add a gate.

No decision changes under any profile, built-in or custom: the read-only branch was already winning. The existing test asserted the unreachable intent rather than the behaviour, so it is replaced by one that pins the answer for both constructions. TestPolicy_SafeProfileDeniesDestructiveAndAllowsRead already pinned it from the policy side.

K-05 — prune undo backups that no run can reach (bd9547b)

The undo stack is in-memory and reset whenever a BackupManager is constructed, so a backup tree left by a session that has already exited cannot be restored from by anything. Nothing read those files again; they accumulated under ~/.packetcode/backups until someone noticed the disk. BackupManager.Cleanup existed but had no production caller.

session.PruneBackups removes trees whose newest write is older than the retention window, once per start, before anything writes new ones. Three things are worth calling out:

  • Age is judged by the newest mtime anywhere in the tree, not the session directory's own. Backups mirror the source tree, so an active session's top directory can look stale while a file three levels down was just written. There is a test for exactly that.
  • The tree belonging to the session that is starting is never a candidate, because a resumed session is routinely older than the window.
  • Pruning is best effort and silent. A tree that cannot be walked or removed is kept, since failing to reclaim disk is not a reason to fail a startup, and only names that pass the session-id check are joined onto the base, so a candidate path cannot escape the backups directory.

Two keys configure it, following the conventions already in [behavior] rather than inventing one. backup_retention_days defaults to 14 and treats a negative value as unset the way every other cap there does, with doctor reporting it. backup_prune_disabled is the off switch, matching loop_detection_disabled and post_edit_diagnostics_disabled. Overloading the sign of a count to mean "disabled" would have contradicted its neighbours.

K-03 — run the end-to-end smoke on all three runners (b1dd6fb)

The smoke job proved the binary starts. It did not exercise a single turn of the agent loop, so every behaviour the audit hardened was covered only by unit tests and by a script nobody ran in CI. smoke.sh drives the real loop against the stdlib-only stub provider on loopback: credential resolution, an approved write, the fail-closed approval path, the dotenv secret refusal, and the compound-command deny floor. No credentials, no network, no new dependencies. It exits nonzero when any assertion fails, so it gates rather than reports. shell: bash is explicit because the Windows runner would otherwise pick PowerShell.

The Makefile comment said to add smoke-e2e to ci "once it has run green on all three runners". That evidence now exists: smoke passes on ubuntu-latest, macos-latest and windows-latest. The macOS and Linux runs were the first ever; the script had only been exercised on Windows before.

What is out, and why

  • K-02 (x/crypto and the Go language floor) — see below; this is now the one thing standing between the repository and a green vulncheck, but it is a decision about who can build the project, not an audit call.
  • K-07 (retire internal/tools/safefs.go via LocalBackend.Resolve) — moves a security boundary. It needs its own review and does not belong in a batch of small changes.
  • K-08 — a warning about what not to start in a low-capability window, not a task.

K-02 is now the blocker for a green CI, and it is cheaper than the audit thought

All 7 remaining reachable advisories are in golang.org/x/crypto v0.43.0, every one of them reached through internal/computers/ssh_backend.go. The fix splits in two, which the audit did not know:

advisories fixed in required go directive
GO-2026-5013, 5017, 5018, 5019, 5020 x/crypto v0.52.0 go 1.25.0
GO-2026-6354, 6355 x/crypto v0.56.0 go 1.26.0

go.mod is currently at go 1.24.2. So five of the seven are reachable by a one-minor floor raise to 1.25, and only the last two need the 1.26 floor that docs/audit/patches/P10b-x-crypto-v0.56.0-go1.26.patch applies. Either way the Go version in README.md and HANDOFF.md moves with it. Both options are on the table; neither is taken here.

K-04, partly answered

U-02 is closed: there is no provider base_url set anywhere in the tree, so there is no plain-http provider endpoint to find. U-01 is still open.

Two incidental observations from the same sweep, neither actioned here: no session under ~/.packetcode/sessions had read a .env file, and internal/doctor/ exists on disk as an empty directory that git does not track, which is what BACKLOG.md describes.

Local verification

Run on windows/amd64 with go1.26.2 against the branch tip:

check result
go build ./... exit 0
go vet ./... exit 0
go test -count=1 ./... exit 0, 55 packages ok
bash smoke.sh 27 passed, 0 failed
gofmt on every changed Go file clean
packetcode doctor with backup_retention_days = -3 names the setting and the config file; silent for a valid value

The new prune tests were checked against a negative control: forcing PruneBackups to return early made TestPruneBackups_RemovesStaleTreesOnly fail, and restoring it made the suite pass again. The workbook generator was re-run and still produces the PDF.

CI results, job by job

Green: all six build jobs, release dry run, and smoke on all three runners (the jobs this PR adds).

Red, every one of them also red on main at 96865bb, the commit this branch forks from:

job cause evidence it pre-dates this PR
vulncheck x/crypto v0.43.0, the 7 advisories above fails on main, where it reports 15 rather than 7
lint .golangci.yml is in the v1 format; golangci-lint v2.9.0 rejects it with unsupported version of the configuration: "". The lint job has not actually linted anything for some time identical failure on main
TUI golden and protocol safety make tui-golden-check exits 1 identical failure on main, same target
test (ubuntu-latest), test (macos-latest) TestApp_Undo_RestoreAndDepth, TestKillTreeOnExitedProcessIsConfirmed, TestKillTreeBeforeReapIsUnconfirmedWithReason same test names fail on main's run
test (windows-latest) TestRunUserPromptSubmit_CollectsStdout times out at 5s waiting on a PowerShell hook the identical timeout, same test and same 5s budget, failed on main in both prior runs (c1bca77, d646094)

The Windows one deserves a note because it is intermittent rather than constant. Across the three most recent main runs it failed twice and passed once; on this branch it failed twice, including a deliberate re-run, and then passed on the final run. Same test, same 5s budget, same PowerShell startup timeout every time. It is a pre-existing flake in a package this PR does not touch.

None of these are in scope here, but lint is worth someone's attention: it is not merely failing, it is not running.

🤖 Generated with Claude Code

packetloss404 and others added 5 commits September 5, 2026 10:07
1.26.3 was the pin from the initial workflow and five patch releases have
shipped since. The toolchain that builds the release binaries should not
be the stale one. Both workflows read the same GO_VERSION key, so they
move together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… had

RequiresApproval returned true for the foreground tool, but every built-in
profile lists collect_agent_results in permissions.readOnlyTool, and that
check allows the call before RequiresApproval is ever consulted. The gate
did not exist. Two sources described the tool and they disagreed.

This aligns the code with the behaviour the policy actually implements and
with docs/advanced-guide.md, which already documents collection as
automatic with an explicit session rule as the way to re-add a gate. No
decision changes for any profile: the read-only branch was already
returning allow first.

The existing test asserted the unreachable intent rather than the
behaviour, so it is replaced by one that pins the answer for both the
foreground and background constructions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The undo stack is in-memory and reset whenever a BackupManager is
constructed, so a backup tree left by a session that has already exited
cannot be restored from by anything. Nothing read those files again; they
only accumulated under ~/.packetcode/backups until someone noticed the
disk. BackupManager.Cleanup existed but had no production caller.

PruneBackups removes trees whose newest write is older than the retention
window, once per start, before anything writes new ones. Age is judged by
the newest mtime anywhere in the tree rather than the session directory's
own: backups mirror the source tree, so an active session's top directory
can look stale while a file three levels down was just written. The tree
belonging to the session that is starting is never a candidate, because a
resumed session is routinely older than the window.

Pruning is best effort and silent. A tree that cannot be walked or removed
is kept, since failing to reclaim disk is not a reason to fail a startup,
and only names that pass the session-id check are joined onto the base, so
a candidate path cannot escape the backups directory.

Two keys configure it. backup_retention_days defaults to 14 and treats a
nonsensical value as unset, matching every other [behavior] cap and
reported by doctor; backup_prune_disabled is the off switch, matching the
loop_detection_disabled and post_edit_diagnostics_disabled idiom rather
than overloading the sign of a count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The smoke job proved the binary starts. It did not exercise a single turn
of the agent loop, so every behaviour the audit hardened -- credential
resolution, the fail-closed approval path, the dotenv secret refusal, the
compound-command deny floor -- was covered only by unit tests and by a
script nobody ran.

smoke.sh drives the real loop against the stdlib-only stub provider on
loopback. It needs no credentials, no network and no new dependencies, and
it exits nonzero when any assertion fails, so it gates rather than reports.
bash is named explicitly because the Windows runner would otherwise pick
PowerShell.

The Makefile comment said to add smoke-e2e to `ci` once the script had run
green on all three runners. This change is what produces that evidence, so
the target moves at the same time and the merge is gated on the result.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four of the eight items are done, one is partly answered, and three stay
open. The list said what was predicted; it now says what the work actually
involved, including the correction that CI moved to Go 1.26.8 rather than
the 1.26.6 the entry named, because 1.26.8 was the current patch release
when the change was made.

K-02 and K-07 are left out of the branch on purpose and the entries say
why: one decides who can build the project, the other moves a security
boundary and needs its own review.

The generator and the markdown carry the same list, so both move together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T15:24:07.225927Z 46d4d5e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46d4d5e64e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml

env:
GO_VERSION: '1.26.3'
GO_VERSION: '1.26.8'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pin a Go patch release that actually exists

As of this commit's 2026-09-05 date, Go 1.26.8 has not been released, so actions/setup-go cannot resolve this exact version. Every job in .github/workflows/ci.yml fails during toolchain setup, and the identical pin in .github/workflows/release.yml also prevents releases; use the current available 1.26 patch release instead.

Useful? React with 👍 / 👎.

Comment thread cmd/packetcode/runtime.go
// Undo cannot reach a previous run's backups, so they are dead weight on
// disk. Reclaim the stale ones once per start, before anything writes new
// ones, and never touch the tree this session is about to use.
session.PruneBackups(opts.BackupsDir, rt.SessionID, backupRetention(opts.Config.Behavior))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve backups belonging to other live runtimes

When more than one runtime is alive, age does not establish that a backup tree is unreachable. For example, an ACP server retains multiple independent runtimes in Server.sessions; if session A made a backup and remains open beyond the configured retention period, creating session B calls this pruning path and deletes A's tree because only B's ID is protected. A subsequent /undo in A then loses its still-live backup, so pruning needs to exclude all active sessions or use a process/activity marker rather than treating every non-current tree as exited.

Useful? React with 👍 / 👎.

The first CI run on this branch turned two predictions into numbers.

K-01: govulncheck reports 15 reachable vulnerabilities on main at Go
1.26.3, 8 of them in the standard library, and 7 on this branch at 1.26.8
with none in the standard library. The pin cleared 8 stdlib advisories,
not the 9 the audit predicted.

K-02: the 7 that remain are all x/crypto v0.43.0, every one reached
through internal/computers/ssh_backend.go, so vulncheck fails on main and
on every branch until the decision is made. It also splits in two, which
the audit did not know: five are fixed in v0.52.0 behind a go 1.25.0
floor, and only the remaining two need v0.56.0 and a go 1.26.0 floor. A
one-minor raise buys five of seven, which is a cheaper option than the
prepared patch and worth having on the table before deciding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@packetloss404
packetloss404 merged commit 0a77376 into main Sep 5, 2026
11 of 16 checks passed
@packetloss404
packetloss404 deleted the chore/day-31-backlog branch September 5, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant