FLUX 3 Image (flux-3-image) generates and edits from one prompt, with up to ten references and bounding boxes. The endpoint rejects seed, output_format and webhooks, and 4k renders can take minutes, so the node polls up to 120 attempts and keeps the result lossless. BFL requires polling the polling_url returned at submit: it points at the region holding the task, and FLUX 3 Image returns a regional one. post_request now returns a TaskId carrying it and get_result polls it for every node, falling back to get_result?id= only when it is absent. A failed task reported as HTTP 503 with a terminal status now stops at once instead of polling until the timeout. Adds an example workflow and documents the polling behavior.
10 KiB
CLAUDE.md
Attribution. The four behavioral rules below ("Think Before Coding", "Simplicity First", "Surgical Changes", "Goal-Driven Execution") are reproduced verbatim from Andrej Karpathy's
CLAUDE.md, distributed via theforrestchang/andrej-karpathy-skillsrepository (transferred tomultica-ai/andrej-karpathy-skillsin May 2026). Used verbatim with attribution; no edits to wording. Project-specific rules follow under## BFL Project Rules.
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
1. Think Before Coding
Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
2. Simplicity First
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
3. Surgical Changes
Touch only what you must. Clean up only your own mess.
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
4. Goal-Driven Execution
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
BFL Project Rules
This is a ComfyUI custom-nodes package that wraps the Black Forest Labs (BFL) FLUX image-generation API. Single Python package, ~1000 LOC, no test framework, ruff for lint/format.
Always do
- Inheritance: every new generation node inherits from
BaseFlux(nodes/base.py). Every finetune node inherits fromBaseFinetuneFlux(same file). The base class ownspost_request/get_result/process_result/create_blank_image/check_multiple_of_32— do not reimplement those. - Node registration:
- Python class name is bare (e.g.,
FluxPro11,FluxDev). The one exception isFluxConfig_BFLwhere the class name matches its registry key for historical reasons. - ComfyUI registry key in
NODE_CLASS_MAPPINGSends with_BFL:"FluxPro11_BFL": FluxPro11. - ComfyUI display name in
NODE_DISPLAY_NAME_MAPPINGSends with(BFL):"FluxPro11_BFL": "Flux Pro 1.1 (BFL)". CATEGORYis one of"BFL"(generation),"BFL/Finetune","BFL/Utility","BFL/Utils","BFL/Config". Match the existing module's choice.- A brand-new module file under
nodes/is invisible until its name is appended tonode_listin__init__.py:4-9.
- Python class name is bare (e.g.,
- Logging: every outbound request and every status line is prefixed
[BFL]so it's grep-able in the ComfyUI console. - curl-equivalent: every POST prints the full curl-equivalent (URL, headers, body) before sending. Matches the pattern in
nodes/base.py:60-62. Useful for users debugging the BFL API directly. - Width / height validation: when a node takes
width/heightints, the request path runsself.check_multiple_of_32(width, height)(already called byBaseFlux.generate_imageif they're inarguments). Newer Flux 2 / Ultra models useaspect_ratiostrings ("16:9","1:1", …) and bypass dimension validation. - Seed handling:
seed = -1means random. The node parameter defaults to-1. Only insertarguments["seed"] = seedinto the request body whenseed != -1— never send-1to BFL. - Optional argument inclusion: build the request
argumentsdict with required keys, then conditionally add optional ones only when non-default / non-empty (if webhook_url:,if image_prompt:, …). Matches every node innodes/api_node.py. - Graceful failure: on any unhandled exception, on terminal API status (
Error,Content Moderated,Request Moderated), or on polling exhaustion, returnself.create_blank_image()— a black 512×512 IMAGE tensor. ComfyUI workflows must never crash because of a BFL failure. - Polling: 5-second interval, max 40 attempts (~200 s ceiling). Already implemented in
BaseFlux.get_result— do not write a second polling loop in a subclass. Slow endpoints pass a largermax_attemptsinstead (Flux 3 Video 240, Flux 3 Image 120). - Config resolution: every generation node accepts an optional
configinput of typeBFL_CONFIG(the output ofFluxConfig_BFL). Pass it through tosuper().generate_image(url_path, arguments, config); the base class hands it toget_config_loader(config)fromnodes/config_node.py:57-67, which decides whether to use theconfig.ini-backed default loader or build one from the override. Both paths must work —config=None(use file) andconfig=<dict>(override). - Conventional Commits 1.0.0:
<type>[scope][!]: <description>. Types:feat,fix,refactor,chore,docs,style,perf,test,build,ci. Scopes:api,finetune,config,utils,web,docs,release,tooling. Description: lowercase, imperative ("add", not "added"/"adds"), no trailing period, ≤ 72 chars. Body explains why, wrapped at 72. Use!after type/scope for breaking changes. No Jira prefix. NoCo-Authored-By: Claude/ "Generated with…" footer. Do NOT inspectgit logto derive style — historical commits usedupdate:and similar non-standard types; the new standard starts now. - Version bumps: edit
pyproject.tomlversionfield and renameCHANGELOG.md's## [Unreleased]heading to## [X.Y.Z] — YYYY-MM-DD— the publish workflow turns that section into the registry's changelog (.github/scripts/registry_changelog.pykeeps the first column of each table row, so keep those entries short and plain). Commit message:chore(release): bump version to X.Y.Z. Then tag that commitgit tag -a vX.Y.Z -m "vX.Y.Z"and push both withgit push --follow-tags.
Never do
- Never commit a real
X_KEYinconfig.ini. The tracked placeholder must stayyour-key. A PreToolUse hook (block-api-key-commit.sh) blocks Edit/Write toconfig.iniwith any other value. If the user needs a real key locally, they edit it after clone and the placeholder stays in the repo. - Never log the full
x-keyheader value, webhook secret, or BFL API response body that contains either. - Never call BFL endpoints from
nodes/utils.pyor other helper modules. Network I/O lives inside generation / finetune / credits node classes only. - Never bypass hooks with
--no-verify. Fix the underlying issue. - Never auto-commit. The user runs
/commit. - Never add a dependency to
requirements.txt/pyproject.tomlwithout an explicit user ask — the package currently depends only ontorch(transitively onrequests,Pillow,numpyvia ComfyUI). Keep it that way. - Never edit
web/js/"while I'm here". The ComfyUI frontend extensions are sensitive to ComfyUI version drift; touch them only when the user asks for a UI change.
Gotchas
ConfigLoaderinnodes/config.py:85-86constructs a module-level singletonconfig_loaderat import time. Some nodes still reference that singleton; new code should useget_config_loader(config)fromnodes/config_node.py:57-67(which respects the per-call override) instead.BASE_URLinconfig.inimay or may not end with/.ConfigLoader.create_urlusesurljoin— pass thepathargument without a leading slash, otherwiseurljoinwill strip the existing prefix path.- Finetune endpoints are regional:
api.us.bfl.ai,api.eu.bfl.ai. The defaultapi.bfl.ai/v1/does not serve finetune routes. Region selection is viaFluxConfig_BFL'sregionfield or per-call. Seenodes/finetune.py. - BFL's async pattern: POST to
flux-pro-1.1(etc.) returns{"id": "<task_id>", "polling_url": "..."}. BFL requires polling that returnedpolling_url— it points at the region holding the task (FLUX 3 Image returns a regional one), so don't build the URL yourself.BaseFlux.post_requestreturns aTaskId(astrsubclass) that carries it, andBaseFlux.get_resultpolls it; only a plain id with no polling URL falls back toget_result?id=<task_id>(query parameterid, not a path segment). If you write a one-off POST, pass itspolling_urlthrough the same way. - The newer Flux 2 / Klein nodes (
nodes/api_node.pylower half) sometimes inlineself.post_request+self.get_resultinstead of callingsuper().generate_image(...). Both styles exist in the codebase; if you're scaffolding a new node, prefersuper().generate_image(...)— it's shorter and runs the dimension check for you. __init__.pydoes dynamic import of every entry innode_list. A new node file innodes/<name>.pyis invisible to ComfyUI until you append"<name>"to that list.WEB_DIRECTORY = "./web"in__init__.py:21exposesweb/js/*to ComfyUI's frontend. Renaming or movingweb/breaks the frontend extensions silently — they just won't load.