Unfortunately, I wasted about an hour assuming I was doing something wrong before realizing I could dig into the development app server and debug. The issue is that, at least with the development app server on Windows, urlfetch.fetch(url) will discard any query parameters in the url passed in. This works fine on the actual hosted service.
I filed a a bug on google code, but you can fix it locally in the meantime. The relevant file is in your appengine install folder, in google/appengine/api. Edit urlfetch_stub.py, search for "connection.request" and fix it. I modified mine to:
# Up to request, then s/path/path+query/
path_and_query = path
if len(query) > 0:
path_and_query = path_and_query + '?' + query
connection.request(method, path_and_query, payload, adjusted_headers)
Which still leaves off the fragment, but I'm OK with that.