Skip to content

fix(skillforge): model fallback chain + diagnostics for upload_packages - #21

Merged
criptogus merged 1 commit into
mainfrom
claude/fix-skillforge-author-normalization
May 23, 2026
Merged

criptogus merged 1 commit into
mainfrom
claude/fix-skillforge-author-normalization

Conversation

@criptogus

Copy link
Copy Markdown
Owner

Users report MCP+OAuth+upload work end-to-end but SkillForge silently fails to normalise uploaded content into private drafts. Two compounding issues were hiding the cause.

Root causes (both invisible to operators)

  1. Single-model author — generateDraft hard-coded google/gemini-3-flash-preview with no fallback. Any structured-output regression on that specific preview SKU kills every upload across the site (web + MCP).
  2. Errors swallowed — processBulkUpload caught exceptions per file and put e.message into results[].error. upload_packages then buried that inside the results array. MCP clients typically render only the first chunk of a tool response, so users (and the LLM driving the call) saw "failed: 1" with no actionable cause.

What's in

src/lib/admin/author.server.ts

  • Walk an ordered model fallback chain (gemini-3-flash-preview → gemini-2.5-flash → gemini-2.5-pro → gpt-5.1-mini). First success wins.
  • Every attempt logs [skillforge.author] <model> failed: … to console.error. Recovery from a non-primary logs a console.warn so a primary regression shows up in logs instead of from user complaints.
  • Final failure throws a structured Error summarising every attempt, so the caller can surface a real message.

src/lib/uploads/uploads.server.ts

  • Per-file failures log [uploads.processBulkUpload] user=… file=…: for correlation against support reports.

src/lib/mcp/tools/skills.ts (upload_packages)

  • New top-level error_summary field on failures (first 3 errors, joined). Survives MCP response truncation.
  • next_step adapts: success points at /account/packages; full-failure tells the user to retry and read error_summary.
  • Idempotency cache only stores successful runs so a transient model outage doesn't poison the key for 24h.
  • Unexpected throws now also log.

Test plan

  • Upload a markdown file via MCP upload_packages with all model env keys present → success, draft appears in /account/packages.
  • Temporarily break the primary model (set an invalid LOVABLE_API_KEY for one model, or point the gateway at a 404) → fallback kicks in; Vercel logs show [skillforge.author] google/gemini-3-flash-preview failed: followed by recovered on google/gemini-2.5-flash.
  • Break ALL models → MCP response shows failed: 1, error_summary: "<filename>: SkillForge author failed across all fallback models — …" and idempotency key is NOT cached (retry with same key actually retries).
  • Successful upload with idempotency_key → second call with same key replays cached response.

https://claude.ai/code/session_019gMoupKKTVydpNwiiACQRd


Generated by Claude Code

Users report MCP+OAuth+upload all work, but the SkillForge author
pipeline silently fails to normalise content into private drafts.

Two root causes were possible and both were invisible:

1. The author flow hard-coded "google/gemini-3-flash-preview" with no
   fallback. Any structured-output regression on that specific model
   (it's a preview SKU) kills every upload across the site.
2. Errors were swallowed: processBulkUpload caught the exception per
   file and put `e.message` into `out.error`, then upload_packages
   buried that inside `results[]`. MCP clients typically render only
   the first chunk of a tool response, so users (and the LLM driving
   the call) saw "failed: 1" with no actionable cause.

Changes:

* generateDraft now walks an ordered model fallback chain
  (gemini-3-flash-preview → gemini-2.5-flash → gemini-2.5-pro →
  gpt-5.1-mini). First success wins. Recovery from a non-primary
  model is logged with the upstream errors so we can spot a primary
  regression in the logs instead of from user reports.
* Every author attempt logs the model + error to console.error so the
  real cause (model rejection, schema mismatch, gateway 4xx) is visible
  in Vercel logs.
* processBulkUpload logs per-file failures with user_id + filename for
  correlation.
* upload_packages now emits a top-level `error_summary` field listing
  the first 3 per-file errors so clients that truncate the response
  still see the cause. Idempotency cache only stores successful runs
  so a transient model outage doesn't poison the key for 24h.
* next_step copy adapts: success path points at /account/packages,
  full-failure path tells the user to retry and look at error_summary.

https://claude.ai/code/session_019gMoupKKTVydpNwiiACQRd
@criptogus
criptogus marked this pull request as ready for review May 23, 2026 02:37
@criptogus
criptogus merged commit 7c3330e into main May 23, 2026
1 check passed
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.

2 participants