Skip to content
1Claw Academy
Curriculum/Integrations & Ecosystem2 minIntermediate · Lesson 2 of 11

Lab: handle an error that never throws

Lab

Wrap an SDK call in try/except, watch it fail anyway, and find where the error actually was.

Every SDK in this course returns { data, error, meta } rather than raising. The lesson said to check error before reading data. This is what happens when you do the thing that feels more careful instead.

  1. 1

    Call an endpoint you are not permitted to read, wrapped in the handler most people write.

    bash
    cat > /tmp/env.py <<'EOF'
    def get_secret(path):
        if path.startswith("prod/"):
            return {"data": None,
                    "error": {"type": "forbidden",
                              "message": "no policy grants this path"}}
        return {"data": {"value": "sk-live-9f2a"}, "error": None}
    
    print("the handler that looks careful:")
    try:
        resp = get_secret("prod/db/root")
        value = resp["data"]["value"]
        print("  got:", value)
    except Exception as e:
        print(f"  caught: {type(e).__name__}: {e}")
    
    print("\nwhat the call actually returned:")
    resp = get_secret("prod/db/root")
    print("  error:", resp["error"])
    print("  data: ", resp["data"])
    
    print("\nthe check that works:")
    resp = get_secret("prod/db/root")
    if resp["error"]:
        print("  handled:", resp["error"]["message"])
    EOF
    python3 /tmp/env.py
  2. 2

    The exception you caught is not the error that occurred.

    text
    the handler that looks careful:
      caught: TypeError: 'NoneType' object is not subscriptable
    
    what the call actually returned:
      error: {'type': 'forbidden', 'message': 'no policy grants this path'}
      data:  None
    
    the check that works:
      handled: no policy grants this path
  3. 3

    Clean up.

    bash
    rm /tmp/env.py

A permissions problem arrived as a TypeError about NoneType, several lines away from the call that failed, with the real message sitting untouched in a field nobody read.

  • The try/except did catch something, which is what makes this so durable. Your error handling appears to work and your logs fill with type errors that describe the symptom rather than the cause.
  • The same shape hides retryable conditions. A 402 payment-required or an approval-required response is a value in error, so code that only handles exceptions treats both as an unexpected crash.
  • It is worse in TypeScript, where resp.data is typed as possibly null and the compiler will tell you, right up until someone adds a non-null assertion to make the build pass.
Tip

Check error first on every call. It is more verbose than optimistic destructuring and it is the difference between a log line naming your actual problem and one describing where your program happened to fall over.

Watch out

If you want the exception-style ergonomics, write one wrapper that raises on error and use it everywhere. What does not work is a codebase where some calls check and others do not.

Check your understanding

3 questions
1

Why did the try block catch a TypeError rather than a permissions error?

2

What else does this pattern hide?

3

What is the recommended fix?