langgenius / langgenius/dify

Start-variable `max_length` is saved as a float the runtime rejects: publish succeeds, then every run fails

Open
#42,287 0 comments 1 reaction 1 assignee Claimed by @jp-sft View on GitHub
Dominant language
TypeScript
Stars
156k
Forks
24.6k
Avg merge
22h 9m
Merged PRs (30d)
610

Description

### Self Checks

- [x] I have read the [Contributing Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) and [Language Policy](https://github.com/langgenius/dify/issues/1542).
- [x] This is only for bug report, if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general).
- [x] I have searched for existing issues, including closed ones.
- [x] I confirm that I am using English to submit this report.
- [x] Please do not modify this template and fill in all the required fields.

### Dify version

1.17.0 (reproduced there; the same validation code is still present on `main` @ 6afe07f9, unfixed)

### Cloud or Self Hosted

Self Hosted (Docker)

### Steps to reproduce

1. Create a **Workflow** app.
2. In the **Start** node, add a variable of type **Paragraph**, e.g. `content`.
3. Set its **Max length** to `100000000000000` (1e14) — a plain integer, far inside the `int64` range. The intent is simply "no practical limit".
4. **Save** the draft, then **Publish**. Both succeed, with no warning or error.
5. Run the app (Run App, or via the service API). It fails immediately, in ~300 ms.
6. Reopen the very same field in the Start node. It now reads **`1e+29`** — not the value that was entered.

| 1️⃣ Before publishing | 2️⃣ After publish + run | 3️⃣ Reopening the same field |
| :--: | :--: | :--: |
| Max length set to 100000000000000 | Run fails with a VariableEntity validation error | The field now shows 1e+29 |
| Max length entered as `100000000000000` | The workflow no longer runs | The same field now holds `1e+29` |

**The entered value does not survive the round trip**, and what is persisted into the graph is a float in exponential notation:

```json
{"variable": "content", "label": "Content", "type": "paragraph", "required": true, "max_length": 1e+29}
```

### The failure depends on the JSON *type*, not on how large the number is

Measured directly against `VariableEntity` on 1.17.0:

| Value in the graph JSON | JSON type | Loads at runtime |
| --- | :--: | :--: |
| field left empty → `null` | — | ✅ |
| `256`, `100000` | int | ✅ |
| `1000000000000000000` (1018) | int | ✅ |
| `10000000000000000000000000000000000` (1034) | int | ✅ |
| `1e+18` | float | ✅ |
| **`1e+19`** and above | float | ❌ `int_parsing_size` |
| `1e+29` (observed above) | float | ❌ `int_parsing_size` |

An integer literal is accepted at **any** magnitude — even 1034. The same quantity written as a float is rejected from `1e+19` onward. Two independent thresholds combine to make this easy to hit and hard to understand:

- JavaScript serializes a number in exponential notation past `1e21`, so the UI writes a **float** into the graph.
- Pydantic then refuses to coerce a float to `int` from `1e+19` upward.

Neither boundary is documented or enforced in the editor, and the field offers no other way to express "no limit" than a large number.

### Root cause: the write path never validates the variable

| | Write path (save / publish) | Read path (run) |
| --- | --- | --- |
| Code | `validate_graph_structure` | `VariableEntity` |
| Start-node `variables` inspected | ❌ never | ✅ |
| `max_length` type-checked | ❌ never | ✅ `int \| None` |
| Result | accepted and published silently | workflow refuses to load |

`WorkflowService.validate_graph_structure` (`api/services/workflow_service.py:1802`) is the only graph validation called by both `sync_draft_workflow` (line 447) and `publish_workflow` (line 709). Its own docstring describes it as lightweight, and it only checks for an empty graph, start/trigger coexistence, and `human-input` nodes:

```python
def validate_graph_structure(self, graph: Mapping[str, Any]):
"""
Validate workflow graph structure.

This performs a lightweight validation on the graph, checking for structural
inconsistencies such as the coexistence of start and trigger nodes.
"""
```

It never inspects the Start node's `variables`, and `VariableEntity(...)` is not instantiated anywhere on the write path (`api/services/`, `api/controllers/`). At **run** time the same value is parsed into an integer-typed field:

```python
class VariableEntity(BaseModel):
...
max_length: int | None = None
```

### ✔️ Expected Behavior

A configuration that can be published should be loadable. Any one of these would prevent the failure:

- the Max length field keeps the value that was typed, instead of returning it in a form the backend cannot parse;
- the value is serialized as an integer, or the backend accepts a float with an integral value;
- the field is bounded (or rejected) at input time, with a documented maximum;
- **publishing fails loudly**, naming the offending node and variable, instead of shipping a workflow that cannot start.

### ❌ Actual Behavior

Publishing succeeds. Every subsequent run then fails immediately with:

```
1 validation error for VariableEntity
max_length
Unable to parse input string as an integer, exceeded maximum size [type=int_parsing_size, input_value=1e+29, input_type=float]
```

Three things make this considerably worse than a plain validation error:

| Problem | Consequence |
| --- | --- |
| No `workflow_run` row is created | The workflow dies while its configuration is loaded, before any run exists. The failure leaves **no trace in the app's run history** — it only surfaces to the API caller. |
| The message identifies nothing | It names neither the app, nor the node, nor the variable, and the value it reports is not the value that was typed. Locating the field required querying `workflows.graph` directly in the database. |
| The app is permanently broken | Both the published version and the draft carry the value, so re-publishing the current draft reproduces it. Every API call fails until the field is cleared by hand in the UI. |

Clearing the field (empty = `None` = no limit) is the only reliable workaround.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.