feat: ComfyUI fallback + session fixes#1538
feat: ComfyUI fallback + session fixes#1538roseonlineownz-lab wants to merge 31 commits intomoeru-ai:mainfrom
Conversation
Character Schema: - Add visibility, nsfwEnabled, nsfwLevel, relationshipMode - Add personaProfile with personality, scenario, speakingStyle - Add starterMessages and boundaries fields - Migration 0008_lazy_wallflower.sql Frontend Updates: - CharacterDialog: New persona tab with NSFW/relationship controls - CharacterItem: Show relationship/NSFW badges - v2/index.vue: Better explore page with filters - v2/[id].vue: New character detail page with profile view Types & Stores: - Updated CharacterBaseSchema with all new fields - CharacterPersonaProfile interface added - Local-first character store compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Character schema: visibility, nsfwEnabled, nsfwLevel, relationshipMode, personaProfile - CharacterDialog: persona tab, NSFW controls, relationship settings - CharacterItem: badges for relationship/visibility/NSFW - v2/index.vue: filters, hero, categorization - v2/[id].vue: character detail page with scenario, starters, boundaries - Migration: 0008_lazy_wallflower.sql Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c5ccd9346f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }, | ||
| }) | ||
|
|
||
| const fallbackHistory = await fetchComfyHistory(promptId, env.COMFYUI_FALLBACK_BASE_URL ?? comfyBaseUrl) |
There was a problem hiding this comment.
Keep ComfyUI host consistent in fallback history checks
In the custom-workflow retry path, the prompt is resubmitted to env.COMFYUI_BASE_URL, but this history lookup uses env.COMFYUI_FALLBACK_BASE_URL ?? comfyBaseUrl. When a fallback URL is configured and differs from the primary host, the worker polls the wrong ComfyUI instance, so successful jobs stay pending and can eventually be marked failed by timeout.
Useful? React with 👍 / 👎.
| .post('/jobs', async (c) => { | ||
| const user = c.get('user')! |
There was a problem hiding this comment.
Enforce NSFW gating before accepting NSFW job requests
This endpoint creates and queues NSFW jobs for any authenticated user without checking the new gating fields (adultVerified, allowSensitiveContent, contentTier). That means clients can bypass the UI restriction by calling the API directly and still generate NSFW jobs, which defeats the account-level gating added in this change.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive NSFW companion system, featuring user gating (adult verification and content tiers), character metadata (visibility, NSFW levels, and persona profiles), and a server-backed image generation pipeline using ComfyUI. The implementation includes database migrations, new API routes, a dedicated image consumer worker, and extensive frontend UI components. Review feedback identifies critical bugs in the ComfyUI job submission and history reconciliation logic, specifically addressing URL mismatches when using fallback servers and the requirement to use the prompt_id returned by the API for reliable job tracking.
| }, | ||
| }) | ||
|
|
||
| const fallbackHistory = await fetchComfyHistory(promptId, env.COMFYUI_FALLBACK_BASE_URL ?? comfyBaseUrl) |
There was a problem hiding this comment.
There is a critical URL mismatch here. The prompt was submitted to env.COMFYUI_BASE_URL (line 417), but history is being looked up on env.COMFYUI_FALLBACK_BASE_URL ?? comfyBaseUrl. If a fallback URL is configured, this lookup will always fail because the job doesn't exist on the fallback server yet. Additionally, it should use the prompt_id returned by the submission.
const actualPromptId = fallbackSubmission.prompt_id ?? promptId
const fallbackHistory = await fetchComfyHistory(actualPromptId, comfyBaseUrl)| workflow: fallbackWorkflow, | ||
| comfy: { | ||
| ...comfyMeta, | ||
| baseUrl: env.COMFYUI_FALLBACK_BASE_URL ?? comfyBaseUrl, |
There was a problem hiding this comment.
Setting baseUrl to env.COMFYUI_FALLBACK_BASE_URL ?? comfyBaseUrl here is inconsistent with the submission at line 417 (which uses env.COMFYUI_BASE_URL). If FALLBACK_BASE_URL is defined, the job metadata will point to the fallback server while the prompt was actually sent to the primary server. This will cause history reconciliation to fail in subsequent polls.
| baseUrl: env.COMFYUI_FALLBACK_BASE_URL ?? comfyBaseUrl, | |
| baseUrl: comfyBaseUrl, |
| logger.withFields({ jobId, promptId }).warn('Discarding invalid custom ComfyUI workflow override and retrying with default workflow') | ||
|
|
||
| const fallbackResponse = await fetch(new URL('/prompt', env.COMFYUI_BASE_URL), { | ||
| method: 'POST', |
There was a problem hiding this comment.
The fallback submission is hardcoded to use env.COMFYUI_BASE_URL. If the job was already being retried on a fallback server (e.g., due to a previous GPU kernel error), this will force it back to the primary server, which might still be failing. It should use comfyBaseUrl instead.
const fallbackResponse = await fetch(new URL('/prompt', comfyBaseUrl), {| }, | ||
| }) | ||
|
|
||
| const history = await fetchComfyHistory(promptId, comfyBaseUrl) |
There was a problem hiding this comment.
The history lookup should use the prompt_id returned by the ComfyUI submission response to ensure it reconciles the correct job, especially if ComfyUI generated a new ID.
| const history = await fetchComfyHistory(promptId, comfyBaseUrl) | |
| const history = await fetchComfyHistory(submission.prompt_id ?? promptId, comfyBaseUrl) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bacd366fcb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }) | ||
| logger.withFields({ jobId, promptId, fallbackBaseUrl: env.COMFYUI_FALLBACK_BASE_URL }).warn('Retrying image job on ComfyUI fallback host after GPU kernel failure') | ||
| return |
There was a problem hiding this comment.
Keep fallback retries pending instead of returning success
In the GPU-kernel fallback branch, the handler sets the job back to queued and then returns normally; in createMqWorker, a successful return path is ACKed, so this event is removed from the stream and no new event is published for the queued retry. In environments where fallback is needed, affected jobs can get stuck in queued forever and never actually resubmit to the fallback ComfyUI host.
Useful? React with 👍 / 👎.
| await mediaService.updateImageJobStatus(jobId, 'submitting', { | ||
| errorMessage: null, | ||
| params: { | ||
| ...job.params, | ||
| workflow, |
There was a problem hiding this comment.
Submit prompt before transitioning job to submitting
The job status is moved to submitting before the /prompt HTTP call is attempted. If that fetch throws (network blip, DNS, connection reset), the worker leaves the message pending, but later retries only poll history for this prompt ID and eventually time out instead of re-attempting submission. This turns transient submit failures into deterministic job failures.
Useful? React with 👍 / 👎.
The 'on' function is not a named export from 'node:process' in Node 22. Changed to use process.on() directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merges latest upstream changes including lockfile updates, instrumentation, and new features while preserving fork's ComfyUI fallback and NSFW gating additions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds missing valibot@catalog: specifier to lockfile for packages/server-schema, fixing CI frozen-lockfile failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove duplicate db property in buildApp call - Remove unused integer import from nsfw-media schema - Add missing nsfwMediaService and nsfwImageMq to test deps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 238373b43a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const job = await mediaService.createImageJob({ | ||
| ...result.output, | ||
| userId: user.id, | ||
| params: result.output.params ?? {}, | ||
| }) |
There was a problem hiding this comment.
Prevent orphaned jobs when event publish fails
This route writes the image_jobs row before publishing to Redis, so if nsfwImageMq.publish(...) throws, the request fails but the queued job is already persisted without any stream event to drive processing. The new consumer only handles stream messages (worker.run(...onMessage...)) and does not reconcile queued DB rows, so these jobs can remain stuck forever and retries create duplicate jobs for the same request.
Useful? React with 👍 / 👎.
| const item = await mediaService.createGalleryItem({ | ||
| ...result.output, | ||
| userId: user.id, | ||
| }) |
There was a problem hiding this comment.
Validate gallery imageJobId ownership before insert
The gallery endpoint accepts and stores any client-provided imageJobId without checking that the referenced job belongs to the caller. Because gallery status/result hydration and job-to-gallery updates are keyed by image_job_id, a user who obtains another user's job ID can bind that job to their own gallery row and observe its status/media result updates, creating cross-account data leakage.
Useful? React with 👍 / 👎.
- Auto-fix lint issues - Sync with moeru-ai/airi upstream Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae8e5e1c73
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| log "starting on http://$HOST:$PORT" | ||
| ( | ||
| cd /home/faramix/airi |
There was a problem hiding this comment.
Resolve project path dynamically before starting web server
The launcher always runs cd /home/faramix/airi before invoking Vite, so start fails on any machine where the repo is checked out elsewhere (including CI or other developers' environments) with a directory-not-found error. This makes the helper script effectively unusable outside one specific filesystem layout; derive the repo root from the script location instead of hardcoding a user path.
Useful? React with 👍 / 👎.
Security: exclude Android keystore files from version control Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52f29d696b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| env: | ||
| BUTLER_API_KEY: ${{ secrets.GAME_PUBLISHING_ITCHIO }} |
There was a problem hiding this comment.
Remove stray top-level env block from release workflow
The trailing env: mapping after the last upload step is indented outside the step/job structure, which makes .github/workflows/release-tamagotchi.yml invalid YAML. With this parse error, GitHub Actions cannot load the workflow, so release runs fail before any build job starts.
Useful? React with 👍 / 👎.
- Change AUTH_GOOGLE_CLIENT_ID/SECRET and AUTH_GITHUB_CLIENT_ID/SECRET from required to optional with empty string defaults - Conditionally include social providers only when credentials are set - Server starts cleanly without OAuth configured (social login disabled) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ef02d1dd8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ...job.params, | ||
| comfy: { | ||
| ...comfyMeta, |
There was a problem hiding this comment.
Drop stored workflow when scheduling fallback retries
When a GPU-kernel failure triggers the fallback path, the queued update keeps ...job.params, which preserves the previously submitted workflow. On the next pass, handleMessage reads that value via extractWorkflow(job.params) and treats it as an override, so buildDefaultComfyWorkflow never rebuilds with fallback settings (checkpoint/steps/dimensions). In practice this can keep retrying with the original heavy workflow even after switching baseUrl, so fallback retries may fail for the same reason instead of recovering.
Useful? React with 👍 / 👎.
| .use('*', authGuard) | ||
| .get('/jobs', async (c) => { | ||
| const user = c.get('user')! | ||
| const jobs = await mediaService.listImageJobs(user.id, 'nsfw') |
There was a problem hiding this comment.
Align job listing filter with accepted route values
This list endpoint always filters to 'nsfw', but the create schema for the same route accepts both 'normal' and 'nsfw'. If a client submits a 'normal' job (which the API currently allows), it is created successfully but will never appear in /api/v1/nsfw/jobs, so clients cannot observe status/results through this API surface. Either reject 'normal' at create time here or make listing route-aware.
Useful? React with 👍 / 👎.
Summary
COMFYUI_FALLBACK_BASE_URLsupport for GPU/CPU fallback pattern.envto prevent accidental commits of secretsKey Changes
apps/server/src/services/nsfw-image-consumer-handler.tsapps/server/src/libs/env.tsCOMFYUI_FALLBACK_BASE_URLenvironment variablepaseo/packages/server/src/server/agent/providers/claude-agent.tsTest Plan
🤖 Generated with Claude Code