langgenius/dify · error

{str(e)}

Error message

{str(e)}

What it means

HTTP 400 returned by the daily workflow runs statistics endpoint (GET /apps/<app>/workflow/runs/daily). The handler wraps `libs.datetime_utils.parse_time_range` in a try/except ValueError and re-emits the exception text via `abort(400, description=str(e))`. `parse_time_range` raises ValueError for malformed time strings (expected `YYYY-MM-DD HH:MM`), unparseable timezones, or when `start > end`.

Source

Thrown at api/controllers/console/app/workflow_statistic.py:116

        200,
        "Daily runs statistics retrieved successfully",
        console_ns.models[WorkflowDailyRunsStatisticResponse.__name__],
    )
    @setup_required
    @login_required
    @account_initialization_required
    @with_current_user
    @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
    @get_app_model
    @model_validate(WorkflowStatisticQuery)
    def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):

        assert account.timezone is not None

        try:
            start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
        except ValueError as e:
            abort(400, description=str(e))

        response_data = self._workflow_run_repo.get_daily_runs_statistics(
            tenant_id=app_model.tenant_id,
            app_id=app_model.id,
            triggered_from=WorkflowRunTriggeredFrom.APP_RUN,
            start_date=start_date,
            end_date=end_date,
            timezone=account.timezone,
        )

        return jsonify({"data": response_data})


@console_ns.route("/apps/<uuid:app_id>/workflow/statistics/daily-terminals")
class WorkflowDailyTerminalsStatistic(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        session_maker = sessionmaker(bind=db.engine, expire_on_commit=False)

View on GitHub (pinned to ef8544b173)

Solutions

  1. Format both `start` and `end` query params as `YYYY-MM-DD HH:MM` (e.g. `2024-08-12 09:30`) in the client before sending.
  2. Validate client-side that `start <= end` before issuing the request.
  3. Confirm the authenticated account has a valid IANA timezone string in `account.timezone` (set in account profile/settings).
  4. If you need ISO 8601 / date-only input, normalize to `YYYY-MM-DD HH:MM` at the API boundary rather than relying on the default format.

Example fix

// before
fetch(`/apps/${appId}/workflow/runs/daily?start=2024-08-01T00:00:00Z&end=2024-08-12`)
// after
const fmt = (d: Date) => d.toISOString().slice(0,16).replace('T',' ')
fetch(`/apps/${appId}/workflow/runs/daily?start=${fmt(start)}&end=${fmt(end)}`)
Defensive patterns

Strategy: validation

Validate before calling

import datetime, pytz

def valid_range(start: str | None, end: str | None, tzname: str) -> bool:
    fmt = "%Y-%m-%d %H:%M"
    s = datetime.datetime.strptime(start, fmt) if start else None
    e = datetime.datetime.strptime(end, fmt) if end else None
    pytz.timezone(tzname)  # raises if invalid
    return not (s and e and s > e)

# call before GET /apps/<id>/workflow/runs/daily
if not valid_range(req.start, req.end, account.timezone):
    raise ValueError("bad range")

Type guard

from typing import Optional
import re

DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$")

def is_workflow_stat_time(v: Optional[str]) -> bool:
    return v is None or bool(DATE_RE.match(v))

Try / catch

try:
    resp = client.get(f"/apps/{app}/workflow/runs/daily", params={"start": s, "end": e})
except HTTPError as err:
    if err.response.status_code == 400:
        # parse_time_range failed: fix format/order and retry once
        ...
    raise

Prevention

When it happens

Trigger: Calling GET /console/api/apps/<app_id>/workflow/runs/daily?start=...&end=... with a start/end value that fails `datetime.strptime(value, "%Y-%m-%d %H:%M")`, or with `start` later than `end`. The account timezone comes from `account.timezone` and must be a valid pytz zone; an unknown zone raises from `pytz.timezone(tzname)`.

Common situations: Frontend sending ISO 8601 (`2024-01-01T00:00:00Z`) or date-only strings instead of `YYYY-MM-DD HH:MM`; date picker returning end-before-start ranges; account record with a null/invalid `timezone` (the `assert account.timezone is not None` just above would actually trip first as an AssertionError, but a non-None invalid tz hits this path).

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/579b12cac330def4. Report an issue: GitHub.