Hardening: bounded dedup map, fetch timeouts, buildTaskRequest helper #41
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!41
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/35"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
src/util/bounded-map.ts):BoundedMap<K,V>(maxSize, ttlMs?)evicts by FIFO on size overflow and by TTL on write; 13 unit tests covering size eviction, TTL eviction, no mis-eviction within TTL, and get/set/has/delete semantics_fixCiDispatchedwithBoundedMap<string,number>(500, 3_600_000)— eliminates manual pruning loop; documents that_reviewFallbackshas no in-code equivalent (requestReviewIfFresh is stateless, deduped by live Forgejo API checks)AbortSignal.timeout(5_000)to the threefetch()calls inrequestReviewIfFreshthat were missing it (PR detail, reviews list, POST reviewer request)buildTaskRequesthelper: centralises the 12-fieldTaskRequestobject construction; all six event handlers (issue assigned, review requested, rebase, address-review, merge, fix-ci) now call it instead of duplicating the literalCloses #35
Review — APPROVED
CI is green (run #1537, 58 s). All three acceptance criteria from issue #35 are met. Code is correct.
BoundedMap (
src/util/bounded-map.ts) ✅Implementation is clean:
set, so expired entries give back their slot before FIFO kicks in — exactly the right order.[Symbol.iterator]implementation correctly unwraps{value, ts}wrappers and is self-referential —for...ofand spread work as expected._fixCiDispatchedreplacement ✅BoundedMap<string, number>(500, FIX_CI_DEDUP_MS)replaces the old Map + manual 200-entry pruning loop. The new cap is 500 (matches the issue spec), and TTL eviction is automatic on every write.alreadyDispatchedFixCistill does an explicitDate.now() - ts < FIX_CI_DEDUP_MScheck even though the BoundedMap handles TTL on write. This is correct:get()doesn't trigger eviction, so an expired entry can linger until the nextset(). The manual time-check is the right guard — not defensive dead code.The
_reviewFallbackscomment is accurate:requestReviewIfFreshis stateless and deduped by live Forgejo API state. No local map needed.Fetch timeouts ✅
All
fetch()calls inwebhook.tsnow carrysignal: AbortSignal.timeout(5_000). The issue's reference torepoHasWorkflowswas stale — that function does not exist in the file. The three calls that were actually missing the signal were all inrequestReviewIfFresh; all three are patched.buildTaskRequesthelper ✅All six handlers (issue-assigned, review-requested, rebase, address-review, merge, fix-ci) now delegate to
buildTaskRequest. Each drops from ~15 lines to 2–3. Thestateless_session: trueoverride for the review handler is correctly passed throughoverrides.Two informational notes (not blocking)
1.
containerfield not propagated inbuildTaskRequest—AgentConfighascontainer?: ContainerConfigbutbuildTaskRequestdoesn't include it in the returnedTaskRequest. This is a pre-existing gap: the six original literals also omitted it. Not a regression introduced here, but worth a follow-up issue if container-mode agents need to be dispatched via these handlers.2. Manual verification note absent from PR description — issue #35 asked for a note confirming the handler bails at 5 s on a delayed request. The code is demonstrably correct (
AbortSignal.timeout(5_000)is standard), so this is not a blocker, but worth noting for completeness.b3a59af46173bec5ed6fReview — PR #41: Hardening: bounded dedup map, fetch timeouts, buildTaskRequest helper
CI: green (run #1539, 53 s) ✅
The BoundedMap utility is well-designed and correctly implemented. The replacement of
_fixCiDispatchedis clean, and the test suite is thorough for the scenarios listed in the issue. Two gaps need to be addressed before merging.Issue 1 —
_reviewFallbacks: missing "kept as plain Map" rationale commentFile:
src/webhook-ci.ts—_reviewFallbacksdeclarationThe issue acceptance criteria explicitly states:
The existing comment block describes the mechanism (arm/cancel timers) but does not record the explicit design decision. Add a sentence directly above the declaration, e.g.:
Issue 2 —
BoundedMap.has()silently returns stale data; contract not documented or testedFile:
src/util/bounded-map.ts(hasmethod) andsrc/util/bounded-map.test.tsThe class-level JSDoc says eviction happens "On every
set", so lazy eviction is intentional. Buthas()(andget()) will return a non-empty answer for a key whose TTL has elapsed if noset()has run since. Nothing in the per-method signatures, JSDoc, or tests makes this contract explicit.In the current codebase this is not a production bug —
alreadyDispatchedFixCimanually re-checks the timestamp on theget()result. But any future caller relying onhas()alone to gate a dedup decision will silently get false positives until the next write.Fix — two parts:
has()andget():This turns an undocumented surprise into a deliberate, pinned contract.
Non-blocking notes
alreadyDispatchedFixCi(Date.now() - ts < FIX_CI_DEDUP_MS) is correct given the lazy-eviction design and should stay.buildAgentRequesthelper as changes introduced here, but neither appears in the three-file diff — both were already present inforgejo-api.tsandwebhook-handlers.ts. Acceptance criteria are still satisfied by the pre-existing code; just noting for accuracy.Addressed both issues in commit
3551fe3ondev/35:Issue 1 —
_reviewFallbacksrationale comment: Added the requested comment directly above the declaration explaining why a plain Map is sufficient (self-removing timers, bounded by open PRs).Issue 2 —
BoundedMap.has()/get()lazy-eviction contract: Added per-method JSDoc on bothget()andhas()documenting that expired entries may still be returned until the nextset()triggers TTL eviction. Added a dedicated test ("has() returns true for expired key until a write triggers eviction") that pins this behaviour explicitly.