Skip to content

fix(api): handler_history limit parsing, recs[:0] alias, nolint comment clarity (FOLD-1043) #1061

Closed

Description

@cristim

Problem

Three code-quality findings in `internal/api/handler_history.go` identified during systematic review (source: `docs/code-review/01-api-handlers.md`, findings 01-M1, 01-L1, 01-L2-history).

01-M1 (Medium) — `parseHistoryFilters` limit parsing: silent swallow, no 400

File: `handler_history.go:406-413` (parseHistoryFilters limit block)

`fmt.Sscanf` silently ignores parse failures. A caller that sends `limit=abc` or `limit=9999999999` gets no feedback and the limit silently falls back to the default. This violates the project memory rule `feedback_strict_int_parse` which requires `strconv.Atoi` and a 400 on error.

Fix: Replace `fmt.Sscanf(s, "%d", &limit)` with `strconv.Atoi`; return a 400 ClientError on parse failure; clamp `limit < 1` to default before applying the `MaxListLimit` cap.

01-L1 (Low) — `handler_history.go:46` — misleading `//nolint:gocritic` comment on `append` line

File: `handler_history.go:64` (`all := append(completed, extra...)`)

The nolint comment says "intentional new slice" but the `gocritic appendAssign` lint fires on `x = append(x, ...))` when the right-hand side extends the same variable. The alternative is to allocate `make([]T, 0, len(completed)+len(extra))` and then append, which is clearer and avoids a potentially confusing comment. If the nolint comment is correct and the lint rule is a false positive, the comment must explain why: the current wording does not.

Fix: Either (a) replace with `all := make([]config.PurchaseHistoryRecord, 0, len(completed)+len(extra)); all = append(all, completed...); all = append(all, extra...)`, or (b) clarify the nolint comment with the exact reason the lint fires here.

01-L2 (Low, history site) — `filterPurchaseHistoryByAllowedAccounts` uses `purchases[:0]` alias

File: `handler_history.go:929` (filterPurchaseHistoryByAllowedAccounts)

`filtered := purchases[:0]` is the append-in-place pattern: filtered and purchases share the backing array, so appending to filtered overwrites the input slice if the caller holds it. In this handler the input `all` is consumed nowhere else after the call, but the pattern is fragile and the project memory (`feedback_deep_copy_state`) cautions against shared backing arrays in slice operations. The dashboard handler site has the same issue (routed to FOLD-1041).

Fix: Allocate `filtered := make([]config.PurchaseHistoryRecord, 0, len(purchases))` so the filter produces a fresh slice.

Evidence

  • 01-M1: `docs/code-review/01-api-handlers.md`, finding M1
  • 01-L1: `docs/code-review/01-api-handlers.md`, finding L1
  • 01-L2 (history site): `docs/code-review/01-api-handlers.md`, finding L2

Proposed fix location

All three fixes land as additional commits on PR #1043 (`fix/history-get-readonly-scoping`), which already modifies `internal/api/handler_history.go`.

Activity

  1. added 2 commits that reference this issue on Jun 7, 2026
    a74990b
    ee34869
  2. added
    pr-createdA PR has been opened for this issue (dedup guard for the auto-PR loop)
    pr-mergedThe PR for this issue has been merged
    on Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort/xsTrivial / one-linerimpact/fewLimited audiencepr-createdA PR has been opened for this issue (dedup guard for the auto-PR loop)pr-mergedThe PR for this issue has been mergedpriority/p2Backlog-worthyseverity/mediumModerate harmtriagedItem has been triagedtype/bugDefecturgency/this-sprintWithin the current sprint

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions