The issue is that compojure's GET is a macro, not a function. Macros run at compile time, so it doesn't get to see what is the value of "v". See the difference between the code generated for h2 and h3:
user> (pprint (macroexpand-1 '(GET ["/foo/:id" :id #"[0-9]+"] [] "hello from foo")))
(compojure.core/make-route
:get
(clout.core/route-compile "/foo/:id" {:id #"[0-9]+"})
(clojure.core/fn
[request__1858__auto__]
(compojure.core/let-request
[[] request__1858__auto__]
"hello from foo")))
user> (pprint (macroexpand-1 '(GET v [] "hello again from foo")))
(compojure.core/make-route
:get
(if (clojure.core/string? v) (clout.core/route-compile v) v)
(clojure.core/fn
[request__1858__auto__]
(compojure.core/let-request
[[] request__1858__auto__]
"hello again from foo")))
In the first case the macro is able to split the route into its constituents and transform it. In the second case the macro just sees the "v" symbol.