The release process (at least for an agent-only release) puts the prepared agent bundle into the proposed stream, so that it can try upgrading to the new version. Unfortunately that doesn't work for a release with a final release version number (like 2.2.9) - the upgrade-juju command can't see the 2.2.9 binaries despite them being in the stream.
I thought this was because environs/tools.PreferredStream considered 2.2.9 to be a released version number so it would only look in the released stream. Reading the code now I can see that was wrong - it prefers the environment's selected agent stream if that's set and not "released", and in the job output I can see that the controller was bootstrapped with agent-stream=proposed. So I'm not sure why this is happening.
A possible fix would be to add an --agent-stream option to upgrade-juju - that would mean that we search that stream (and any lower-risk ones) for candidates.
See http://ci.jujucharms.com/job/release-juju-verify-upgrade/53/console
Between filing this bug and 2.8.0, juju has added --agent-stream to upgrade-juju and upgrade-controller.
However there is still an issue. juju is able to verify the version and start the upgrade. Then the upgrade worker uses the model-config agent-stream to download and fails if not also set to proposed.
Side note: if you upgrade to say 2.8.1 proposed, you cannot currently then upgrade to 2.8.1 released. Version checks use the version, not a (version, stream) tuple. In the rare case we spin a new candidate, the user would be left on the first one.