Troubleshooting
Common issues and solutions when using Skyulf.
"Unknown transformer type"
Your step's transformer string does not match any registered node in the NodeRegistry.
Fix: Check spelling and casing. Use the exact key from the Configuration page (e.g., OneHotEncoder, not one_hot_encoder).
"Unknown model type: ..."
The type value in your modeling config is not registered.
Fix: Ensure you are using one of the 20 supported model keys listed in Configuration - Modeling config. If using XGBoost, install the extra: pip install skyulf-core[modeling-xgboost].
Resampling errors about non-numeric columns
Oversampling / undersampling (SMOTE, RandomOverSampler, etc.) require all features to be numeric.
Fix: Apply an encoder (e.g., OneHotEncoder or OrdinalEncoder) before any resampling step in your preprocessing config.
SMOTE not found / ImportError
The imbalanced-learn package is an optional dependency.
Fix:
pip install skyulf-core[preprocessing-imbalanced]
XGBoost ImportError
XGBoost estimators are optional and not installed by default.
Fix:
pip install skyulf-core[modeling-xgboost]
Optuna / TuningCalculator ImportError
Hyperparameter tuning with Optuna requires the tuning extra.
Fix:
pip install skyulf-core[tuning]
"X merges N parallel branches…" (merge advisory banner)
You see an amber banner in the Results panel like:
Drop Missing Columns merges 2 parallel branches: TransformationNode + MissingIndicator. 6 overlapping columns take values from MissingIndicator; unique columns from the others are kept as-is. For sequential application, chain them instead.
What it means
A node has two or more incoming edges that trace back to a common ancestor (sibling fan-in). The engine merges them per column, using the nearest shared ancestor as a baseline to decide which branch actually changed each column:
- Nobody changed it — the value is identical everywhere; any branch can supply it.
- Exactly one branch changed it — that branch owns the column. Its value always survives, regardless of edge order or merge strategy. This is the common case, and it produces no banner.
- Two or more branches changed it to different values — a genuine conflict. The merge strategy
(
last_winsby default, i.e. the last input edge) picks the winner, the losing branch's edits to that column are discarded, and this banner appears naming the contested columns.
Branches that changed a column but agree on the result (e.g. two MissingIndicator steps emitting
the same *_missing flags) fall under case 2, not case 3: nothing is discarded, so no banner.
The Merge Strategy dropdown in the Properties panel is only shown after a run has detected case 3 for that node — there is nothing to choose when nothing is contested. It names the node it applies to and both contesting branches.
Why it's usually a bug
If both branches modify the same columns (e.g. both rescale SepalLengthCm), only the winning
branch's values survive. You lose the work the other branch did on that column.
How to entirely fix it
Chain the nodes instead of fanning them out. Instead of:
Dataset ──> TransformationNode ──┐
──> MissingIndicator ────┴──> DropMissingColumns
wire:
Dataset ──> TransformationNode ──> MissingIndicator ──> DropMissingColumns
Now MissingIndicator reads TransformationNode's output, adds *_missing flags on top, and passes the combined frame downstream. No fan-in, no warning, all changes preserved.
In the canvas:
- Delete the
Dataset → MissingIndicatoredge. - Drag a new edge from
TransformationNode→MissingIndicator. - Re-run preview — the banner will disappear.
When fan-in is fine
- Disjoint columns — branches output non-overlapping columns. The banner says "No column overlap — all columns from all branches are kept." You can ignore it.
- Redundant edge (e.g.
Splitter → Scalerplus a directSplitter → EncoderwhileScaler → Encoderalso exists) — the engine detects this and suppresses the warning entirely because the descendant supersedes the ancestor under last-wins.
What the engine guarantees
| Pattern | Behavior |
|---|---|
Linear chain A → B → C |
No warning, full data flow |
| Redundant ancestor edge | Suppressed silently |
| Disjoint-column fan-in | Advisory only, all columns kept |
| Overlapping-column fan-in | Last input wins — chain instead |
| Cycles | Engine rejects |
Rule of thumb: if two branches touch the same columns, chain them; if they touch different columns, fanning in is fine.
"training frame contains N non-numeric column(s)" / "All trials failed" after a Split
Training fails with an error like:
Node node_training: training frame contains 1 non-numeric column(s): city. Supervised models can only fit numeric features. After a Split, merged branches resolve overlapping columns by merge order — the last connected branch wins every shared column — …
What it means
Your branches fork after a Split node and merge back into a training node. In that shape,
per-column ownership cannot apply — the nearest shared ancestor is the splitter, whose stored
artifact is a train/test split rather than a frame — so overlapping columns resolve by pure
merge order: with last_wins (the default) the last connected branch wins every shared
column. If an earlier branch encoded a column but the last branch still carries it as raw
text/categories, the raw version wins and the model receives a non-numeric column.
Before this guard existed, the same misconfiguration surfaced as the cryptic "Hyperparameter tuning failed: All trials failed" because every fold failed inside the model fit.
Fix
Pick one:
- Encode on the winning branch — add the encoder (WOE / OneHot / Ordinal) to the branch
whose version of the column wins (the last connected one under
last_wins). - Make every branch fully numeric — each branch imputes/encodes/scales on its own, so the merge result is model-ready no matter which branch wins.
- Make branches disjoint — post-split branches should output non-overlapping columns.
- Drop the column if it is not needed.
Text pipelines: after a vectorizer, set drop_original=True (or drop the raw text column) so the
raw text does not survive into the merge. See also
Multi-Path Pipelines — After a Split: Order Decides Everything.
"…reintroduced N columns removed by an upstream Drop Columns step"
A second, different advisory can appear alongside the fan-in banner:
MissingIndicator merged a branch that reintroduced 1 column removed by an upstream Drop Columns step:
Id. They were dropped again, so any transform applied to them on that branch is discarded.
What it means
A Drop Columns / Drop Missing Columns node is treated as authoritative for its entire subgraph, not just its own branch. When a sibling branch that bypassed it feeds the same merge, the union would resurrect the dropped columns — so the engine removes them again after merging.
The consequence is easy to miss: if the other branch applied a transformation to one of those columns, that work is thrown away with the column.
Fix
Move the Drop Columns node after the merge if you want the columns to survive, or route the transforming branch through the Drop Columns node so both branches agree on which columns exist.
Target column not found after encoding
If your target column is categorical and you apply OneHotEncoder with drop_original=True, the target column may be dropped or expanded.
Fix: Always run TrainTestSplitter (which separates X and y) before encoding. This ensures the target is safely stored in y and never touched by the encoder.
Pickle loading errors
If SkyulfPipeline.load() fails, the saved model was pickled with a different library version.
Fix: Ensure compatible versions of:
- Python (same minor version, e.g., 3.10.x)
- scikit-learn (same minor version)
- pandas (same major version)
Pin versions in requirements.txt for reproducibility across environments.
Polars version mismatch
Skyulf requires polars >= 1.36. Older versions may cause schema or type errors during data ingestion.
Fix:
pip install --upgrade polars
Celery / Redis connection refused (full platform)
The backend's async task queue requires a running Redis instance.
Fix:
- Ensure Redis is running:
redis-cli pingshould returnPONG. - Check
CELERY_BROKER_URLin your.envfile (default:redis://localhost:6379/0). - If using Docker:
docker-compose up redis.
Feature scaling produces NaN
Standard scaling can produce NaN or Inf if a column has zero variance (all identical values).
Fix: Remove constant columns before scaling, or use auto_detect: True in the StandardScaler params - it automatically skips non-numeric and constant columns.