7.8 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 332 | `toolInputPreviewMaxLength` (and `toolTextSummaryMaxLength`) in `config.json` are silently ignored — preview is always truncated at the hardcoded default |
Retro: #332 — Fix toolInputPreviewMaxLength / toolTextSummaryMaxLength loader gap
Stage: Planning (2026-06-08T00:00:00Z)
Session summary
Planned the fix for the loader-pipeline gap that drops toolInputPreviewMaxLength and toolTextSummaryMaxLength.
Confirmed the downstream machinery (normalizePermissionSystemConfig, resolveToolPreviewLimits, ToolPreviewFormatter) is already correct and the break is confined to UnifiedPermissionConfig / normalizeUnifiedConfig / mergeUnifiedConfigs in src/config-loader.ts.
Plan committed at docs/plans/0332-preview-length-config-loader-gap.md.
Observations
- The issue body references
src/runtime.ts, which no longer exists — the relevant save/refresh logic now lives insrc/config-store.ts(ConfigStore.save()/ConfigStore.refresh()). - Schema (
schemas/permissions.schema.json), example (config/config.example.json), anddocs/configuration.mdalready document both fields, so the kuba follow-up comment about the docs schema is stale — no doc edits are needed. - The "secondary"
save()bug fixes itself once the loader is fixed:save()merges via{ ...existing.config, … }andexisting.configis loaded through the same loader, so the spread carries the parsed fields through unchanged. - Decision (confirmed with user): rely on the
...existing.configspread insave()rather than the issue's proposed explicit write ofnormalized.toolInputPreviewMaxLength. The in-memorynormalizedvalue is the merged value; writing it into the global file would bake a project/per-agent override into global. The two preview-length fields are not modal-editable, so leaving the on-disk global value untouched is correct. - The issue's circular-dependency concern about
normalizeOptionalPositiveIntis not literal (neitherconfig-loader.tsnorextension-config.tsimports the other today), but the cleanest home is the dependency-lightsrc/common.tsthat both already import — avoids the loader depending on the higher-level config-shape module. normalizeOptionalPositiveInthas only two references:extension-config.ts(use) andtest/extension-config.test.ts(direct tests). The package skill does not reference it. Relocation is low-risk.
Stage: Implementation — TDD (2026-06-08T20:37:00Z)
Session summary
All four TDD steps completed in a single session.
Four commits landed: relocation of normalizeOptionalPositiveInt to common, parse fix in normalizeUnifiedConfig, merge fix in mergeUnifiedConfigs, and a save-preservation regression guard in config-store.test.ts.
Test count went from 1837 to 1858 (+21 tests across common.test.ts, config-loader.test.ts, and config-store.test.ts).
Observations
- The plan's single combined scalar loop in
mergeUnifiedConfigsrequired splitting into two type-separated loops (boolean scalars, number scalars) because TypeScript rejected assigningboolean | numberto the narrowed per-property type. The type fix was applied during the post-step cleanup and committed as part of the step-3 commit. - Step 4 (save-preservation test) passed immediately on the first run — confirming the spread approach does the right thing once the loader is fixed.
No production code change was needed for
config-store.ts. - A mid-step rebase (mixed reset + re-commit) was required to correct a commit where the type-safety fix accidentally landed in the step-4 test commit rather than the step-3 production commit.
Resolved before push with
git reset HEAD~2and clean re-commits. - Pre-completion reviewer verdict: WARN — the only finding was the missing implementation stage note in this retro file (now addressed).
Stage: Final Retrospective (2026-06-08T21:05:43Z)
Session summary
Shipped the loader-gap fix end to end (plan → TDD → ship → release pi-permission-system v10.5.3), then ran a post-mortem on why the original feature (#266) shipped the bug undetected and filed follow-up issue #356.
The fix was four commits (+21 tests); the post-mortem traced the root cause to a hidden intermediate type (UnifiedPermissionConfig) plus an unknown-typed normalizePermissionSystemConfig parameter that erased the type safety that would have caught the omission.
Observations
What went well
- Planning treated the issue's proposed
save()fix (explicit write ofnormalized.toolInputPreviewMaxLength) as a hypothesis and rejected it in favor of the...existing.configspread, confirmed viaask_user. TDD validated the call: step 4 passed on first run with no production change toconfig-store.ts, and the spread approach avoided baking project/per-agent overrides into the global file. This is theplan-issue"proposed change is a hypothesis, not a spec" rule paying off concretely. - The post-mortem used targeted git archaeology at specific SHAs (
git show 3a7dafbb --stat,git cat-file -e <sha>:<path>,git grep … <sha>) to prove the bug shipped with #266 rather than guessing, then produced a well-scoped follow-up (#356) with two concrete hardening ideas.
What caused friction (agent side)
instruction-violation(self-identified) — committed the step-4 test before runningpnpm run check, so a type error introduced in step 3 (theboolean | numbernarrowing inmergeUnifiedConfigs) surfaced only at the end-of-cycle check. Thetestingskill already says to runpnpm run checkimmediately after a step that changes a shared interface; it was not applied after the interface-touching steps. Impact: the fix then had to land in the step-3 commit, butgit commit --amendhit HEAD (step 4); recovery required agit reset HEAD~2+ two clean re-commits (~6 extra tool calls).other— reached forgit rebase -iin a non-interactive environment; it aborted because$EDITORis Neovim. Impact: two failed attempts and a user hint (EDITOR=true) before abandoning it forgit reset+ recommit.other(trivial) — left/tmp/issue-body.mdbehind after filing #356 viagh issue create --body-file. Impact: stray temp file, no rework.
What caused friction (user side)
- The user proactively supplied the
EDITOR=truehint when the rebase aborted — mechanical oversight the agent could have pre-empted by not reaching for interactive rebase in a known non-interactive environment.
Diagnostic details
- Model-performance correlation — session spanned
anthropic/claude-opus-4-8→claude-sonnet-4-6→claude-opus-4-8model changes; thepre-completion-reviewersubagent ran onanthropic/claude-sonnet-4-6(per its frontmatter), appropriate for judgment-heavy review. No mismatch. - Escalation-delay tracking — no rabbit hole exceeded 5 consecutive tool calls on one error; the type error was a single-edit fix and the commit-reorder was a deliberate recovery, not flailing.
- Unused-tool detection — none missed; the post-mortem git archaeology was done directly with targeted commands, which was faster than dispatching a subagent.
- Feedback-loop gap analysis — per-step
vitestverification ran incrementally (good), butpnpm run checkran only at end-of-cycle rather than after the interface-changing steps. This specific gap is the root of the commit-reorder friction and is the actionable finding.
Changes made
.pi/prompts/tdd-plan.md— added a note to the Green step: runpnpm run checkbefore committing a step that adds or changes a shared type/interface (or a consumer over one), since Vitest does not typecheck.AGENTS.md(§ Commits) — added guidance to avoidgit rebase -iin this environment and to reorder/fix unpushed commits withgit reset+ re-commit orGIT_SEQUENCE_EDITOR/EDITOR=true.