diff --git a/canyonos_core/controller/deploy.py b/canyonos_core/controller/deploy.py index f480dc4..f053927 100644 --- a/canyonos_core/controller/deploy.py +++ b/canyonos_core/controller/deploy.py @@ -180,8 +180,24 @@ def _execute_workflow(request_id, kwargs, context=None): @app.route(f"/{fn_name}", methods=["POST"]) def handle_workflow(): """Accept a workflow request, dispatch async, return request ID.""" - # Parse request body as JSON args for the workflow function - kwargs = request.get_json(force=True, silent=True) or {} + # An empty body is a valid no-args call (workflows may have all-default + # params). A non-empty body that isn't a valid JSON object is rejected + # here with the real parse error, instead of being coerced to {} and + # surfacing later as a misleading "missing argument" error from the + # workflow function itself. Parsed with the stdlib json module + # directly (not request.get_json) because Flask/Werkzeug replaces the + # actual decode error with a generic "Bad Request" message. + raw_body = request.get_data(cache=True) + if raw_body: + try: + kwargs = json.loads(raw_body) + except json.JSONDecodeError: + logger.warning("Invalid JSON in request body.", exc_info=True) + return jsonify({"error": "Invalid JSON in request body"}), 400 + if not isinstance(kwargs, dict): + return jsonify({"error": "Request body must be a JSON object"}), 400 + else: + kwargs = {} # Extract policy context (if provided) before passing to workflow context = kwargs.pop("_context", {})