You had a stale cache entry in instance of Chromium when you had the route defined as @app.route("/users/"). You later changed to @app.route("/users") which Chrome still had it cached with the trailing /. Try accessing this simple example using incognito mode and see that /users?uid=1 remaining unchanged and that no 404 is reported. This is what happens when I first accessed it initially (using Chrome 42).
127.0.0.1 - - [07/Jul/2015 14:02:39] "GET /users?target=1 HTTP/1.1" 200 -
Then stopping that script (thanks for that complete almost self-contained example) and add @app.route("/users/") to the list of routes, below the original @app.route("/users/") route (to have a higher order of precedence so that Flask first trigger the redirect), i.e.:
@app.route("/users")
@app.route("/users/")
(Or simply remove the @app.route("/users") decorator)
Now try accessing the same page again in your incognito session, note that in your console:
127.0.0.1 - - [07/Jul/2015 14:04:11] "GET /users?target=1 HTTP/1.1" 301 -
127.0.0.1 - - [07/Jul/2015 14:04:11] "GET /users/?target=1 HTTP/1.1" 200 -
Ah, there's your redirect. Remove that extra line we just added, try going to /users?target=1 again, this is what happens:
127.0.0.1 - - [07/Jul/2015 14:07:22] "GET /users/?target=1 HTTP/1.1" 404 -
Chrome silently rewrites the URL to /users/?target=1 based on the cache entry in the incognito mode, and is reflected because only that URL is showing up on the Flask access log.
If you wish to support both methods, you have to do it this way:
@app.route("/users/")
@app.route("/users")
Then both access methods work:
127.0.0.1 - - [07/Jul/2015 14:08:49] "GET /users/?target=1 HTTP/1.1" 200 -
127.0.0.1 - - [07/Jul/2015 14:08:59] "GET /users?target=1 HTTP/1.1" 200 -
Rather than resulting in:
127.0.0.1 - - [07/Jul/2015 14:10:00] "GET /users?target=1 HTTP/1.1" 301 -
127.0.0.1 - - [07/Jul/2015 14:10:00] "GET /users/?target=1 HTTP/1.1" 200 -