Skip to content

Guard SkillMD submissions against duplicate creation#204

Open
stanleyoz wants to merge 1 commit into
projnanda:mainfrom
stanleyoz:platform/fix-skills-duplicate-submit
Open

Guard SkillMD submissions against duplicate creation#204
stanleyoz wants to merge 1 commit into
projnanda:mainfrom
stanleyoz:platform/fix-skills-duplicate-submit

Conversation

@stanleyoz

Copy link
Copy Markdown

What

Fixes the duplicate-submission bug reported in #203 — one Submit click
on /skills created three identical rows for the same SkillMD.

Why

useActionState's pending boolean only flips true after React
commits a render, which lags a fast click by at least one frame — a
gap wide enough for multiple form submissions to fire before the
button actually disables. Ran into this firsthand re-registering the
StreamPay skill after a hosting migration (see #203).

What changed

Two-layer fix:

  • Client (submit-form.tsx): a synchronous useRef guard on the
    form's onSubmit blocks re-entrant submits immediately, without
    waiting on React's pending state to propagate.
  • Server (actions.ts + skills.ts): findRecentDuplicate()
    rejects an identical (name, source_url/content) submission within
    a 15-second window in submitSkill, returning the existing row
    instead of inserting a new one. This is defense in depth against a
    genuine double POST (retry, slow network) that the client guard
    can't see — the same idempotency-key shape the StreamPay listing
    itself is built around.

Verification

cd apps/nest-dashboard
npx tsc --noEmit -p .   # clean
npx eslint src/app/skills/submit-form.tsx src/app/skills/actions.ts src/lib/skills.ts
# one pre-existing, unrelated error (react-hooks/set-state-in-effect
# on the success-reset effect) — confirmed present on main before
# this change too, not introduced here

No DATABASE_URL/Neon credentials available in the environment this
was written in, so findRecentDuplicate couldn't be exercised against
the live DB — worth a manual double-submit test on /skills before
merging.

Related

Closes the duplicate-cleanup half of #203 going forward (doesn't
retroactively remove the existing duplicate rows — that still needs a
manual DB cleanup as requested there).

A rapid multi-click on the /skills Submit button created three
identical rows for one submission (react-hooks/set-state-in-effect
notwithstanding, useActionState's `pending` only flips true after
React commits a render, which lags a fast click by at least one
frame — enough of a gap to fire multiple form submissions before
the button disables).

Two-layer fix, matching the idempotency-key pattern this registry's
own StreamPay listing is built around:
- Client: a synchronous ref-based guard on the form's onSubmit blocks
  re-entrant submits before React's pending state has a chance to
  propagate.
- Server: findRecentDuplicate() in submitSkill rejects an identical
  (name + source) submission within a 15s window and returns the
  existing row instead of inserting a new one — defense in depth
  against a genuine double POST (retry, slow network) that the
  client guard can't see.

Verified: npx tsc --noEmit and eslint pass on all three changed
files (one pre-existing, unrelated lint error in submit-form.tsx
confirmed present on main before this change too). No DB credentials
available in this environment to exercise findRecentDuplicate against
live Neon — worth a manual double-submit test before merge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014LVcSKyucSnxaoSxd3ccXh
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