Skip to content

Negotiate the API version with the server - #13

Merged
dpleshakov merged 5 commits into
mainfrom
feat/api-version-negotiation
Oct 6, 2026
Merged

dpleshakov merged 5 commits into
mainfrom
feat/api-version-negotiation

Conversation

@dpleshakov

Copy link
Copy Markdown
Owner

Without a configured API version, wit work-items get-batch and wit wiql query-by-wiql failed on servers that refuse a POST request without one, and the user had no way to know which version to set. tfscli now does what the official SDKs do:

  • Without a configured version, each command except auth login first sends OPTIONS {collection}/_apis and uses the version the server lists for its resource. Without a version the server would answer at that same version, so the output does not change.
  • If the negotiation fails for any reason other than 401 or an unreachable server, the request goes out without a version, as before, and --verbose says why in an [info] line. If the server then refuses the request for the missing version, the config error names apiVersion, TFSCLI_API_VERSION, and --api-version.
  • A configured version is sent unchanged, without OPTIONS.

The project brief, CLAUDE.md, the architecture, the README, the agent skill, and the changelog are updated. The research and the decisions are in docs/archive/2026-10-06-tasks-api-version-negotiation.md.

dpleshakov and others added 5 commits October 6, 2026 08:23
The api-version-negotiation backlog entry starts: it is broken down into a
tasks file and removed from the backlog. TASK-08 of the first-run errors
file, the next step for a POST refused for lack of a version, moves into
the new file as TASK-04, rewritten for a refusal that follows a failed
negotiation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
The findings come from the Go SDK, which on-premises servers share with the
others: OPTIONS on the collection's _apis returns every resource location
with its versions, and a location is identified by its id. tfscli matches
by id as the SDK does, since the pair of area and resource name is not
documented as unique and the work items resource already has two
locations. The refusal of a request without a version carries the typeKey
VssVersionNotSpecifiedException in a report from Azure DevOps Services;
the on-premises behaviour and the OPTIONS response of TFS 2015 and 2017
remain unverified, which the fallback covers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
Without a configured version, every request is now preceded by OPTIONS on
the collection's _apis and carries the released version the server lists
for its resource location, or the latest preview when there is none. That
is the version the server would choose for a request without one, so the
output of the GET commands does not change, while the POST commands stop
depending on the server accepting a request without a version.

Each caller names its resource location by the id the SDKs use, so Get and
Post take a location; the login check passes none and stays unversioned.
A 401 or a transport failure on OPTIONS fails the command at once, since
the request itself would fail the same way. Any other failure falls back
to the request without a version, as before, and says why under --verbose
through a new Info method of the logger: the line reports what tfscli did
on its own, which a warning would misdescribe.

The two tasks are committed together because the version selection alone
would be unused code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
When negotiation fails and the server then refuses the request for
carrying no API version, the error now says why the version could not be
negotiated, where a version is set, and that it must not exceed what the
server supports. The refusal is recognised by its typeKey,
VssVersionNotSpecifiedException, and by the start of its English message
only when the server sends no typeKey, since the message follows the
language the server is installed in. A request refused without a fallback
keeps the server's message, as does a refusal of a configured version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
The project brief records the product decision: the API version is
negotiated with the server rather than left out, because some servers
refuse a POST request without one, and the fallback to a request without a
version keeps every server tfscli worked with working. CLAUDE.md, the
architecture, the README, the agent skill, and the --api-version help follow
it, and the changelog records the change for users.

Closes docs/archive/2026-10-06-tasks-api-version-negotiation.md

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
@dpleshakov
dpleshakov merged commit eb2dc7f into main Oct 6, 2026
1 check passed
@dpleshakov
dpleshakov deleted the feat/api-version-negotiation branch October 6, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant