Skip to content

fix: handle 504/non-JSON error responses properly instead of crashing with JSONDecodeError#111

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1772591330-fix-504-error-handling
Open

fix: handle 504/non-JSON error responses properly instead of crashing with JSONDecodeError#111
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1772591330-fix-504-error-handling

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Mar 4, 2026

fix: check HTTP status before JSON parsing to handle 504/error responses

Summary

When the sandbox proxy returns a non-JSON error response (e.g. 504 Gateway Timeout with an HTML body), the async Python SDK was crashing with json.JSONDecodeError ("Expecting value: line 1 column 1") instead of raising a proper ResponseError with the HTTP status code.

Root cause: json.loads(await response.aread()) was called before self.handle_response_error(response) in every async method that uses raw httpx. This meant the JSON parse always ran first and blew up on HTML/non-JSON bodies, preventing the HTTP status check from ever executing.

Fix: Reorder to: read bytes → check HTTP status via handle_response_error() → parse JSON. This matches the pattern already used correctly in the sync SDK.

Affected async methods:

  • process.get(), list(), stop(), kill(), logs()
  • filesystem.mkdir(), write(), write_tree(), read(), rm(), ls(), find(), grep(), and multipart upload helpers

Also improved _exec_with_streaming() error messages (async + sync) to include the HTTP status code.

Review & Testing Checklist for Human

  • Verify the reordering is correct in each changed method — handle_response_error(response) must come after await response.aread() (httpx requires reading before checking) but before json.loads()
  • Confirm the sync SDK methods (sync/process.py get/list/stop/kill/logs) were already correct and intentionally left unchanged
  • In _upload_part, variable was renamed from data to result_data to avoid shadowing the function parameter — verify this doesn't break callers
  • Test end-to-end with an actual 504 response (e.g. by forcing a timeout on a long-running process.exec() or process.get()) to confirm ResponseError is now raised instead of JSONDecodeError

Notes


Note

This PR fixes a bug where async SDK methods called json.loads() before handle_response_error(), causing JSONDecodeError on non-JSON error responses (e.g. 504 HTML pages). The fix reorders to: read bytes → check HTTP status → parse JSON, across all affected async methods in filesystem.py and process.py. The incremental commit adds a reproducer_504.py validation script.

Written by Mendral for commit e39ca55.


Open with Devin

In the async Python SDK, json.loads() was called before
handle_response_error(), so non-JSON error responses (like 504 Gateway
Timeout HTML pages) would crash with JSONDecodeError instead of raising
the proper ResponseError.

Fixed by reordering: read bytes first, check HTTP status via
handle_response_error(), then parse JSON. This ensures that 504 and
other HTTP errors raise ResponseError with status code info rather
than confusing JSON parse errors.

Affected methods:
- process.get(), list(), stop(), kill(), logs() (async)
- process._exec_with_streaming() (async + sync) - improved error message
- filesystem mkdir(), write(), write_tree(), read(), rm(), ls(), find(),
  grep(), and multipart upload helpers (async)

Co-Authored-By: cdrappier <cdrappier@blaxel.ai>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

mendral-app[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

Co-Authored-By: cdrappier <cdrappier@blaxel.ai>
Copy link
Copy Markdown
Contributor

@mendral-app mendral-app Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Code Review

Assessment ⚠️

The core fix (committed in 301ed3b) was already reviewed and approved. The only new change in this update is the addition of reproducer_504.py.

The reproducer script is well-structured and covers all the fixed methods. One issue: the assert_raises_clean_error helper's fallback except Exception branch (lines 135–141) counts any unexpected exception type as a pass (with a WARN label). This means a regression introducing a new unexpected exception type would silently pass the test suite. Consider this if the script is intended to be used as a regression gate.

Note

Tag @mendral-app with feedback or questions. View session

Comment thread reproducer_504.py
Comment on lines +135 to +141
except Exception as e:
if "504" in str(e) or "status" in str(e).lower():
print(f" PASS {method_name}: raised Exception with status info: {e}")
passed += 1
else:
print(f" WARN {method_name}: raised {type(e).__name__}: {e}")
passed += 1 # Still better than JSONDecodeError/AttributeError
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 bug (low)

The fallback except Exception branch counts any unexpected exception as a pass (incrementing passed). If a new bug introduced a different exception type (e.g. ConnectionError, TimeoutError), the test would print WARN but still count as passing and not fail the script. If this script is used as a regression gate, unexpected exceptions should be counted as failures.

📝 Suggested change
Suggested change
except Exception as e:
if "504" in str(e) or "status" in str(e).lower():
print(f" PASS {method_name}: raised Exception with status info: {e}")
passed += 1
else:
print(f" WARN {method_name}: raised {type(e).__name__}: {e}")
passed += 1 # Still better than JSONDecodeError/AttributeError
except Exception as e:
if "504" in str(e) or "status" in str(e).lower():
print(f" PASS {method_name}: raised Exception with status info: {e}")
passed += 1
else:
print(f" FAIL {method_name}: raised unexpected {type(e).__name__}: {e}")
failed += 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant