Skip to content

Instantly share code, notes, and snippets.

@jmchilton
Created January 16, 2026 16:32
Show Gist options
  • Select an option

  • Save jmchilton/d99e70938391c4ca9913ef3ed17b3571 to your computer and use it in GitHub Desktop.

Select an option

Save jmchilton/d99e70938391c4ca9913ef3ed17b3571 to your computer and use it in GitHub Desktop.
Triage for Galaxy issue #21602: restrictOnConnections fails with multiple subworkflows

Issue #21602: Workflow: No propagation of possible options for a workflow input (with Attempt restriction based on connections) if connected to several subworkflows

Author: Bérénice Batut (@bebatut) State: OPEN Created: 2026-01-16T15:35:13Z Labels: None Milestone: None

Description

When running with a workflow input defined with Attempt restriction based on connections and connected to a similar parameter inside a subworkflow, the possible options are displayed if the input is connected to 1 subworkflow (screenshots 1 & 2), but not if the input is connected to several subworkflows (screenshots 3 & 4), even if the subworkflows are the same.

Environment

  • Galaxy Version: 25.1
  • Galaxy Server: useGalaxy.eu
  • Operating System: macOS
  • Browser: Firefox

Steps to Reproduce

  1. Import the "Workflow with 1 subworkflow" (screenshot 1) on usegalaxy.eu
  2. Open the run page
  3. The possible option for the input should be visible (screenshot 2)
  4. Import the "Workflow with 2 subworklows" (screenshot 3) on usegalaxy.eu
  5. Open the run page
  6. No possible option for the input are visible (screenshot 4)

Expected Behavior

Options should be visible for workflow inputs with "Attempt restriction based on connections" even when connected to multiple subworkflows.

Actual Behavior

Options are only visible when connected to 1 subworkflow. When connected to multiple subworkflows (even identical ones), no options are displayed.

Screenshots

Workflow with 1 subworkflow - Works correctly

  • Workflow structure shows input connected to single subworkflow
  • Run page shows available options for the restricted input

Workflow with 2 subworkflows - Issue

  • Workflow structure shows input connected to two subworkflows
  • Run page shows NO options for the restricted input (empty dropdown/selection)

Issue #21602: Code Research

Problem Summary

Workflow input with "Attempt restriction based on connections" (restrictOnConnections: true) works when connected to 1 subworkflow but options disappear when connected to 2+ subworkflows, even if the subworkflows are identical.

Key Code Files

1. Parameter Input Module - Restriction Logic

File: /Users/jxc755/workspace/galaxy/lib/galaxy/workflow/modules.py

The ParameterInputModule class (lines 1221-1700+) handles parameter inputs in workflows.

get_runtime_inputs method (lines 1543-1618):

  • Checks if restrictOnConnections is enabled (line 1562)
  • If enabled with connections, calls restrict_options() (line 1565)
  • Creates a SelectToolParameter with the restricted options
# Line 1562
attemptRestrictOnConnections = is_text and parameter_def.get("restrictOnConnections") and connections
if attemptRestrictOnConnections:
    connections = cast(Iterable[WorkflowStepConnection], connections)
    restricted_options = self.restrict_options(step, connections=connections, default_value=default_value)

2. restrict_options Method (lines 1487-1541)

This is the critical method that collects options from connections.

def restrict_options(self, step, connections: Iterable[WorkflowStepConnection], default_value):
    try:
        static_options: list[list[ParameterOption]] = []
        # Retrieve possible runtime options for 'select' type inputs
        for connection in connections:
            module = connection.input_step.module
            if isinstance(module, ToolModule):
                # ... handles direct tool connections
            elif isinstance(module, SubWorkflowModule):
                subworkflow_input_name = connection.input_name
                for step in module.subworkflow.input_steps:
                    if step.input_type == "parameter" and step.label == subworkflow_input_name:
                        static_options.append(
                            step.module.get_runtime_inputs(step, connections=step.output_connections)[
                                "input"
                            ].static_options
                        )

        options: Optional[list[OptionDict]] = None
        if static_options and len(static_options) == 1:
            # SINGLE CONNECTION - uses options directly
            options = [
                {"label": o[0], "value": o[1], "selected": bool(default_value and o[1] == default_value)}
                for o in static_options[0]
            ]
        elif static_options:
            # MULTIPLE CONNECTIONS - intersection logic (lines 1522-1537)
            intxn_vals = set.intersection(*({option.value for option in options} for options in static_options))
            # ...
    except Exception:
        log.debug("Failed to generate options for text parameter, falling back to free text.", exc_info=True)

3. Workflow Step Model

File: /Users/jxc755/workspace/galaxy/lib/galaxy/model/__init__.py

  • output_connections relationship (line 8755): Connections where this step is the OUTPUT step
  • Used to propagate restrictions through the workflow

4. Existing Tests

File: /Users/jxc755/workspace/galaxy/lib/galaxy_test/api/test_workflows.py

  • test_value_restriction_with_select_from_subworkflow_input (line 7880): Tests restriction with single subworkflow
  • No test for multiple subworkflows exists

Root Cause Theories

Theory 1: Exception Silently Swallowed (HIGH PROBABILITY)

The restrict_options method wraps everything in a try/except that catches ALL exceptions and logs at DEBUG level (lines 1540-1541):

except Exception:
    log.debug("Failed to generate options for text parameter, falling back to free text.", exc_info=True)

When connected to multiple subworkflows:

  1. The intersection logic runs (lines 1522-1537)
  2. An exception occurs (e.g., AttributeError, TypeError)
  3. The exception is silently caught
  4. Method returns None implicitly
  5. No options are displayed

Evidence: The intersection code at line 1524 references options which is None at that point:

intxn_vals = set.intersection(*({option.value for option in options} for options in static_options))

Wait - options here is the inner loop variable from for options in static_options. This is confusing naming but not a bug itself.

Theory 2: static_options Attribute Missing or None (MEDIUM PROBABILITY)

When recursively calling get_runtime_inputs for the inner subworkflow's parameter input:

step.module.get_runtime_inputs(step, connections=step.output_connections)["input"].static_options

The returned parameter object might not have static_options set if:

  1. The inner parameter input doesn't have restrictOnConnections: true
  2. No options were found/set during get_runtime_inputs

In SelectToolParameter.__init__ (basic.py line 987-990):

if self.dynamic_options is None and self.options is None:
    self.static_options = input_source.parse_static_options()

If options ARE provided via parameter_kwds["options"] but parsed as dynamic, static_options won't be set.

Theory 3: Empty Intersection (MEDIUM PROBABILITY)

If the two subworkflows have parameter inputs with slightly different configurations:

  • Different restrictOnConnections settings
  • Different downstream tool connections

The intersection at line 1524 could result in an empty set, causing no options to be displayed.

This would explain why "even identical subworkflows" fail - they may not be truly identical in terms of their internal connections.

Theory 4: Recursive Depth Issue (LOW PROBABILITY)

The recursive call pattern:

  1. Parent input -> restrict_options -> SubWorkflowModule
  2. Inner input -> get_runtime_inputs -> (inner tool connections)

If inner step doesn't have its module properly initialized or connections aren't loaded, it could fail silently.

Recommended Investigation Steps

  1. Enable DEBUG logging and check for "Failed to generate options" messages
  2. Add test case for multiple identical subworkflows with restrictOnConnections: true
  3. Check if static_options exists on the returned parameter object
  4. Verify intersection logic when both subworkflows have identical options

Code Flow Diagram

Parent Workflow Input (restrictOnConnections: true)
    |
    +-- Connection to SubWorkflow 1
    |       |
    |       +-- Inner Parameter Input -> Tool (with select options)
    |
    +-- Connection to SubWorkflow 2
            |
            +-- Inner Parameter Input -> Tool (with select options)

get_runtime_inputs(parent_input, connections=[conn1, conn2])
    |
    +-- restrict_options(connections)
            |
            +-- For conn1 (SubWorkflowModule):
            |       get_runtime_inputs(inner_step, inner_connections)
            |       append static_options[0]
            |
            +-- For conn2 (SubWorkflowModule):
            |       get_runtime_inputs(inner_step, inner_connections)
            |       append static_options[1]
            |
            +-- len(static_options) > 1: use INTERSECTION logic
            |       intxn_vals = set.intersection(...)
            |
            +-- If exception: return None (silent fail)

Questions to Resolve

  1. Is the inner parameter input's restrictOnConnections required for propagation to work?
  2. Does static_options attribute exist on parameter objects created with explicit options?
  3. What's the actual exception being swallowed (if any)?
  4. Are the subworkflow connections (step.output_connections) properly loaded when accessed?

Issue #21602: Importance Assessment

Summary

Workflow inputs with restrictOnConnections: true don't show options when connected to multiple subworkflows. Works with 1 subworkflow, fails with 2+.


1. Severity: MEDIUM

Classification: Functional breakage (feature silently fails)

  • No data loss or security implications
  • No crashes or hangs
  • Feature appears to work but produces incorrect output (no options displayed)
  • Silent failure: exceptions caught at DEBUG level, no user-visible error

2. Blast Radius: Specific configurations only

Affected users:

  • Workflow authors using restrictOnConnections: true on inputs
  • Connected to 2+ subworkflows (not direct tool connections)
  • Both subworkflows must have matching parameter inputs

Not affected:

  • Single subworkflow connections (works correctly)
  • Direct tool connections (works correctly)
  • Users not using this feature

Estimated impact: Low-to-medium. This is an advanced workflow authoring feature, but training workflows often use this pattern.


3. Workaround Existence: Painful

Possible workarounds:

  1. Use a single subworkflow (redesign workflow structure)
  2. Remove restrictOnConnections and use free-text input with documentation
  3. Manually duplicate options in the workflow definition

Assessment: Workarounds require workflow redesign or loss of functionality. Users must know the feature is broken (silent fail makes discovery hard).


4. Regression Status: Likely recent regression (already fixed in dev)

Key finding: Commit b240f9e4273 (April 2, 2025) by @mvdbeek explicitly fixes this:

Fix attempt restriction on multiple connections

Broken by the extra elements in the ParameterOption named tuple
introduced in https://github.com/galaxyproject/galaxy/pull/19659.

Branch status:

  • Fix IS in dev branch
  • Fix IS in release_25.1 branch
  • usegalaxy.eu reports running "25.1" - may be running an older 25.1 point release before the fix was backported

Timeline:

  • PR #19659 introduced the regression (added extra fields to ParameterOption namedtuple)
  • PR #19948 fixed it (April 2025)
  • Issue reported January 16, 2026

Hypothesis: usegalaxy.eu is running a 25.1 release that predates the fix merge into release_25.1, OR the fix wasn't properly backported.


5. User Impact Signals

From issue:

  • Reporter: @bebatut (active GTN trainer, community member)
  • No labels or milestone assigned
  • No reactions visible (issue just opened)
  • No duplicate reports found

Context:

  • Trainers frequently use subworkflows for modular training materials
  • Silent failure means users may have been hitting this without reporting

6. Recommendation: VERIFY DEPLOYMENT / NEXT RELEASE

Immediate actions:

  1. Verify usegalaxy.eu deployment version - check if b240f9e4273 is deployed
  2. If NOT deployed: priority backport/deploy (the fix already exists)
  3. If deployed: investigate further - the fix may be incomplete for subworkflows

Classification rationale:

  • NOT a hotfix - no data loss, security, or widespread breakage
  • NOT backlog - affects real training use cases, fix may already exist
  • Priority should be verifying the fix is deployed, not writing new code

Follow-up if fix is deployed:

If usegalaxy.eu has the fix and still exhibits the bug:

  1. Add test case for 2+ subworkflows (current test only covers 1 subworkflow)
  2. Investigate silent exception in restrict_options try/except block
  3. Check if static_options attribute exists on recursively obtained parameters

Code Location Reference

Critical file: /Users/jxc755/workspace/galaxy/lib/galaxy/workflow/modules.py

Method: InputParameterModule.restrict_options() (lines 1487-1541)

Existing test: test_value_restriction_with_select_from_subworkflow_input (1 subworkflow only)

Missing test: Multiple subworkflows scenario


Unresolved Questions

  1. Exact version deployed on usegalaxy.eu? Is b240f9e4273 included?
  2. If fix is deployed, what's the actual exception being swallowed?
  3. Does the inner subworkflow parameter need restrictOnConnections: true?
  4. Are the test workflows on usegalaxy.eu truly identical subworkflows?

Issue #21602: Fix Plan

Problem Summary

Workflow inputs with restrictOnConnections: true show no options when connected to 2+ subworkflows. Works with 1 subworkflow, fails with 2+.


Diagnosis

Most Probable Cause: Type Mismatch in static_options

The fix b240f9e4273 (April 2025) addressed multiple connections but introduced a type mismatch:

Code path for subworkflows (lines 1509-1513):

step.module.get_runtime_inputs(step, connections=step.output_connections)[
    "input"
].static_options

Returns raw tuples (label, value, selected) from parse_static_options() (yaml.py:447-452).

Intersection code (lines 1524-1528) expects ParameterOption namedtuples:

intxn_vals = set.intersection(*({option.value for option in options} for options in static_options))
# ...
collapsed_labels[option.value].add(option.name)  # <-- AttributeError if tuple

Why Single Connection Works

Single connection (lines 1516-1521) uses index access o[0], o[1] which works with both tuples and NamedTuples.

Why Multiple Connections Fail

Multiple connections use attribute access .value, .name which fails on raw tuples - exception caught silently at line 1541.


Verification Steps

Step 1: Confirm Server Version

# Check usegalaxy.eu version via API
curl -s https://usegalaxy.eu/api/version | jq .

If version is v25.1.0 or later: Fix IS deployed, proceed to Step 2. If version is older: Deployment issue - recommend upgrade.

Step 2: Verify the Bug

  1. Create workflow with 2 identical subworkflows
  2. Both subworkflows have: restrictOnConnections: true parameter -> select tool
  3. Parent has: restrictOnConnections: true parameter -> both subworkflows
  4. Run workflow - check if options appear

Step 3: Check DEBUG Logs

Enable DEBUG logging for galaxy.workflow.modules:

# In galaxy.yml or logging config
galaxy.workflow.modules: DEBUG

Look for: "Failed to generate options for text parameter, falling back to free text."

If present, confirms exception being swallowed.


Fix Plan (If Bug Persists After Deployment)

Option A: Convert Tuples to ParameterOption (Recommended)

Location: lib/galaxy/workflow/modules.py line 1509-1513

Change:

elif isinstance(module, SubWorkflowModule):
    subworkflow_input_name = connection.input_name
    for step in module.subworkflow.input_steps:
        if step.input_type == "parameter" and step.label == subworkflow_input_name:
            raw_options = step.module.get_runtime_inputs(step, connections=step.output_connections)[
                "input"
            ].static_options
            # Convert tuples to ParameterOption
            static_options.append([
                ParameterOption(name=o[0], value=o[1], selected=o[2] if len(o) > 2 else False)
                for o in raw_options
            ])

Option B: Use Index Access Consistently

Make intersection code use index access like single-connection code:

elif static_options:
    intxn_vals = set.intersection(*({option[1] for option in options} for options in static_options))
    intxn_opts = {option for options in static_options for option in options if option[1] in intxn_vals}
    collapsed_labels = defaultdict(set)
    for option in intxn_opts:
        collapsed_labels[option[1]].add(option[0])
    # ...

Tradeoff: Option A is cleaner, Option B is more backwards compatible.


Test Plan

Test 1: Multiple Subworkflows (Missing Test)

def test_value_restriction_with_select_from_multiple_subworkflow_inputs(self):
    workflow_id = self.workflow_populator.upload_yaml_workflow(
        """
class: GalaxyWorkflow
inputs:
  outer_param:
    optional: false
    restrictOnConnections: true
    type: string
steps:
- in:
    inner_param:
      source: outer_param
  run:
    class: GalaxyWorkflow
    label: subworkflow_1
    inputs:
      inner_param:
        optional: false
        restrictOnConnections: true
        type: string
    steps:
    - tool_id: multi_select
      in:
        select_ex:
          source: inner_param
- in:
    inner_param:
      source: outer_param
  run:
    class: GalaxyWorkflow
    label: subworkflow_2
    inputs:
      inner_param:
        optional: false
        restrictOnConnections: true
        type: string
    steps:
    - tool_id: multi_select
      in:
        select_ex:
          source: inner_param
"""
    )
    with self.dataset_populator.test_history() as history_id:
        run_workflow = self._download_workflow(workflow_id, style="run", history_id=history_id)
    options = run_workflow["steps"][0]["inputs"][0]["options"]
    assert len(options) == 5  # Should have intersection of options

Test 2: Different Subworkflow Options (Intersection)

Test with 2 subworkflows connected to tools with DIFFERENT options - verify intersection works.

Test 3: Single Subworkflow Regression

Ensure existing test_value_restriction_with_select_from_subworkflow_input still passes.


Effort Estimate

Task Complexity Time
Verify deployment Low 15 min
Add DEBUG logging Low 15 min
Reproduce issue Medium 30 min
Implement fix (Option A) Low 30 min
Write tests Medium 1 hour
Code review Low 30 min
Total ~3 hours

If deployment issue only: 0 hours dev work.


Decision Tree

Is usegalaxy.eu running v25.1.0 or later?
├── NO → Recommend deployment upgrade. Done.
└── YES → Bug persists after fix b240f9e4273
    │
    Can reproduce locally?
    ├── NO → Env-specific issue (investigate config)
    └── YES → Type mismatch confirmed
        │
        Apply Option A fix
        │
        Add test case for multiple subworkflows
        │
        Submit PR

Unresolved Questions

  1. Exact version on usegalaxy.eu - is b240f9e4273 deployed?
  2. Is there a newer point release of 25.1.x not yet deployed?
  3. Does reporter's workflow have restrictOnConnections: true on BOTH outer AND inner parameters?
  4. If fix is deployed and bug persists, is exception visible in DEBUG logs?

Issue #21602: Triage Summary

Top-Line Summary

Workflow inputs with restrictOnConnections: true fail to display options when connected to 2+ subworkflows, working only with single subworkflow connections. A fix (b240f9e4273 by @mvdbeek, April 2025) already exists in both dev and release_25.1 branches addressing "restriction on multiple connections." If usegalaxy.eu (where bug was reported) hasn't deployed this fix yet, deployment resolves the issue. If the fix IS deployed and bug persists, there's a type mismatch in restrict_options() method (lines 1487-1541 in lib/galaxy/workflow/modules.py) where subworkflow static_options return raw tuples but intersection code expects ParameterOption namedtuples with .value/.name attributes—causing an AttributeError that's silently caught.

Importance Assessment

Criterion Rating Notes
Severity Medium Functional breakage, silent failure (no user error)
Blast Radius Specific configs Only affects 2+ subworkflows with restrictOnConnections
Workaround Painful Requires workflow redesign or feature removal
Regression Yes (fixed in dev) Broken by PR #19659, fixed by PR #19948
Priority Next Release / Verify Deployment Fix exists, verify it's deployed

Questions for Triage Discussion

  1. Deployment verification: Is usegalaxy.eu running a release that includes commit b240f9e4273? (curl https://usegalaxy.eu/api/version)
  2. Workflow configuration: Does the reporter's workflow have restrictOnConnections: true on BOTH the outer AND inner subworkflow parameters?
  3. If fix deployed: Can we enable DEBUG logging to capture the exception from restrict_options()?
  4. Test coverage: Should we add a test for multiple subworkflows before closing? (Current test only covers single subworkflow)

Effort Estimate

Scenario Effort
Deployment issue (fix not deployed) 0 dev hours
Fix incomplete, code change needed ~3 hours
Difficulty to recreate Medium (need specific workflow structure)

Most Probable Fix

If bug persists after deployment verification, convert raw tuples to ParameterOption namedtuples in the subworkflow branch of restrict_options():

# lib/galaxy/workflow/modules.py ~line 1509
raw_options = step.module.get_runtime_inputs(...).static_options
static_options.append([
    ParameterOption(name=o[0], value=o[1], selected=o[2] if len(o) > 2 else False)
    for o in raw_options
])

Regression Source

  • Introduced by: PR #19659 (added extra fields to ParameterOption namedtuple)
  • Fixed by: PR #19948 / commit b240f9e4273 (April 2, 2025)
  • Author of fix: @mvdbeek
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment