Batch-register test scripts from a Git repository as Testany platform cases, and thereafter keep them in sync with the repository via sync / switch / relation.
User input: $ARGUMENTS
Core concepts
| Object | Description |
|---|---|
| Connection | Git platform identity obtained after a one-time OAuth authorization (currently supports GitHub). Under a GitHub connection there are several installation_bindings, each binding represents one GitHub App installation (owner/org + accessible repo scope).installation_id is a required argument for subsequently browsing repositories |
| Import History | One import configuration: bound to a certain connection, a certain repo, a certain ref, determining the mapping of "these files ↔ these cases" |
| File Binding | Under import history, the binding record of "one file ↔ one case"; it is the source of truth for that batch of cases on Testany |
| Sync Record | Audit record of one sync action (per-file success/failure/skip) |
import_mode (enum values must match exactly)
Essential difference: backend auto-diff vs user manual selection — not a scenario judgment like "how much of the repository content is tests".
managed_import: mirror the repository, automatic diff.confirm_git_synctriggers an incremental landing of mirror→binding; newly added files can also go through addFiles separately, and upstream deletions go through the two-phase sourceDeleted confirmationsync_link: explicitly manage the binding set.Under the current phase,confirm_git_syncis unavailable, and binding set evolution uniformly goes through the two relation flows addFiles (new files) and sourceDeleted (upstream deletion).preview_git_syncis used here only to view "changes of already-bound files relative to last_synced_commit"
Selection advice: want peace of mind + trust the repository as source-of-truth → managed_import; want fine-grained control over which files enter the binding set each time → sync_link. Do not infer based on reasons like "whether the repository is pure tests".
sync_mode (enum values must match exactly)
latest: follow the HEAD oftracked_branch, automatically advancing on each syncpinned_commit: pinned to a certain commit; to advance you must go through switchCommit
Operations quick reference
| Intent | Toolchain |
|---|---|
| See which Git connections I have | testany_list_git_connections |
| Create a new GitHub connection | testany_initiate_git_oauth → user completes in browser → poll list |
| Access token about to expire / already expired but refresh_token still exists | testany_refresh_git_connection_scope(server-to-server, no browser needed) |
| Refresh_token is also broken / user actively disconnects / switches account | testany_reauthorize_git_connection(returns authorize_url, requires the user to complete in the browser) |
| Connection-side repo selection / scope change | testany_refresh_git_connection_scope(same entry point; it both refreshes the token and verifies the scope) |
| Delete connection | testany_disconnect_git_connection |
| List accessible repositories | testany_list_git_repositories(required installation_id) |
| Select branch / commit | testany_list_git_branches / testany_list_git_commits |
| View repository directory / preview file | testany_browse_git_tree / testany_preview_git_file |
| List / view / delete imports | testany_list_git_imports / testany_get_git_import / testany_delete_git_import |
| Create new import | testany_create_git_import |
| View / clear bindings | testany_list_git_import_file_bindings / testany_delete_git_import_file_bindings |
| Scheduled sync (managed_import only) | testany_preview_git_sync → testany_confirm_git_sync(sync_link in current phase not supported confirm_sync; use addFiles/sourceDeleted instead) |
| Replay failed sync (sync_link only) | testany_retry_git_sync({sync_record_id}) |
| Switch commit (requires sync_mode=pinned_commit) | testany_preview_git_switch_commit → testany_confirm_git_switch_commit |
| Unpin, return to latest (requires sync_mode=pinned_commit) | testany_preview_git_switch_mode → testany_confirm_git_switch_mode |
| Audit sync history | testany_list_git_sync_records → testany_get_git_sync_record |
| Add new file (both managed_import and sync_link) | testany_get_git_add_files_summary → _list_git_add_files_candidates → _confirm_git_add_files |
| Mark source deleted (both managed_import and sync_link) | testany_get_git_source_deleted_summary → _list_..._candidates → _confirm_git_source_deleted |
| Webhook read / toggle / rotate | testany_get_git_webhook_config / _update_git_webhook_config / _disable_git_webhook / _regenerate_git_webhook_secret |
Phase 1: Connect Git platform
testany_list_git_connections, find the connection for the target platform- Ifthere is no connection →
testany_initiate_git_oauth({platform: "github"})getauthorize_url, hand it to the user's browser to complete the GitHub App installation authorization (the agent cannot click for them; clearly tell them "come back and let me know when done"), then polltestany_list_git_connectionsuntil the new connection appears - Ifthere is a connection, do ahealth check(see connection-health.md):
- config layer:
status == "connected_scope_ready"is what counts as healthy ⚠️ not the literal"ready" - credential layer:
token_expires_atmust be >now + 60s; otherwise first calltestany_refresh_git_connection_scopeto do a server-to-server refresh (without disturbing the user) - Check whether each
installation_bindings[i].scope_verified_atis too old (>24h, optional refresh)
- config layer:
- Once healthy, from
connection.installation_bindings[]pick the targetinstallation_id
Common unhealthy states → quick fix path reference(for the full enumeration see connection-health.md):
status / symptom |
handling |
|---|---|
connected_scope_ready but token_expires_at expired or nearing expiry |
refresh_connection_scope(server-to-server) |
token_expired |
reauthorize_git_connection → browser |
permission_changed |
First refresh_connection_scope; if that fails then reauthorize |
authorized_no_installation |
Use scope_management_entry_url to have the user's browser install the installation |
disconnected |
initiate_git_oauth reauthorize |
Key discipline: do not just look at
status. GitHub App access_token expires after 8h by default; when it expires,statusis stillconnected_scope_ready(a lagging indicator), and only the next GitHub-API call will make the backend flip it totoken_expired. Proactively usingtoken_expires_atto judge can avoid one unnecessary failed retry.
Phase 2: Choose repository and ref
testany_list_git_repositories({connection_id, installation_id, search?})selectowner/repo+repo_full_name- then choose the frame of reference:
sync_mode=latest→testany_list_git_brancheschoosetracked_branchsync_mode=pinned_commit→ also needtestany_list_git_commitsto select the specificpinned_commit
- If you need to verify files, use
testany_browse_git_tree+testany_preview_git_file - Choose runtime:Directly call
testany_filter_case_runtimesto get the list for the user to choose, do not first ask the user what language the test script is in. Most runtimes' executors are consistent; by default recommendCloudPrime-Default(or another CloudPrime series). After the user picks, use the correspondingruntime_uuid. Do not make the user memorize UUIDs. - Call
testany_get_tenant_configto getdeployment_type, for Phase 3 to decide theselected_filesdefault value ofvisibility. Session-level caching is sufficient.
Phase 3: Assemble selected_files and create import
Required fields
When calling testany_create_git_import :
| Field | When required |
|---|---|
connection_id |
Always |
installation_id |
Always |
import_mode |
Always (managed_import or sync_link) |
sync_mode |
Always (latest or pinned_commit) |
tracked_branch |
Always (even in pinned_commit mode, still carry branch context) |
pinned_commit |
Only sync_mode=pinned_commit |
runtime_uuid |
Always |
repo_full_name |
Always, in the form owner/repo |
selected_files |
Both import_mode values are required, as the first-round seed of the binding set |
root_path |
Required, pass "/"when taking the repo root; pass a relative path when taking a subdirectory |
workspace_key |
Optional. As a compatibility default: when per-file does not provide workspace_keys and the case is restricted, use this as a fallback.Do not treat it as requiredMake the user choose from dozens of workspaces |
selected_files how to assemble
For each file to include, provide one FileSelectionInput:
file_path:Relative to the repository root(not relative toroot_path)name: case name; if not provided, use the file nameexecutor:testany_browse_git_treereturned byentry.executoris the backend-recommended value; use it directlytrigger_method: required for multi-config executors (playwright / maven / gradle, etc.); see structure intestany://schema/import-gitcase_labels: firsttestany_list_labelsconfirm it exists (if it does not exist, firsttestany_create_label)visibility/is_private: see "Case visibility policy" below
Case visibility policy
visibility / is_private / workspace_keys rules are constrained by tenant deployment_type .For complete rules, error codes, and how to obtain them, see case-visibility-policy.md。
Points specific to the Import path:
- Phase 2 has already called
testany_get_tenant_config; here directly use the cacheddeployment_typeto decide the per-filevisibilitydefault value - Default policy:
deployment_type=1→ per-filevisibility: "global", no need forworkspace_keys, and no need for top-levelworkspace_keydeployment_type=2→ per-filevisibility: "restricted"+workspace_keys: ["<key>"](or use top-levelworkspace_keyas a fallback), because global is not allowed
- Top-level
workspace_keyis the fallback when per-file does not provideworkspace_keysfallback for;type=1does not need to be passed at all when using global - The user insists on global under
type=2: explain the limitation to the user, downgrade to restricted; do not blindly retry
The backend currently treats private / workspace also as restricted a compatibility alias, but for agent wording and constructing payloads, uniformly using global | restricted is more robust.
After import: prompt that credential-type variables should be changed to be declared with secrets
Import will upload script files as cases, butis not responsible forfilling in environment_variablesfor cases (that is testany-case / testany-sync-case-env-from-source 's responsibility).
After the import is complete, if the script contains these naming patterns, proactively prompt the user to declare them as case_meta.environment_variables in type: secrets + secret_ref, rather than plaintext type: env:
*_PASSWORD/*_PWD*_TOKEN/*_APIKEY/*_API_KEY*_SECRET/*_KEY
After the user confirms secret_ref 's three fields (workspace_key / credential_safe_key / credential_key), the script can directly read the environment variable of the same name to obtain the credential value.
Phase 4: periodic sync
current-phase constraint:
testany_confirm_git_synccurrently only takes effect formanaged_import;sync_linkbinding evolution uniformly goes through addFiles / sourceDeleted.idempotency_keyis automatically generated by MCP,do not manually pass UUIDs—unless you explicitly want deduplication across multiple calls.
managed_import
First obtain this round's testany_preview_git_sync, by Sync Authorization Decision Table verify actual differences against the current permission. Submit only when all differences are within the authorization and the pre-submission state has no conflicts; for differences already explicitly permitted, do not ask for confirmation again:
testany_confirm_git_sync({ import_history_id })
no_changes end; accidental deletions/additions must not be overridden by "just sync" or an old preview authorization. Managed confirm is applied based on the overall mirror difference and cannot pass file_selections to trim out unauthorized items. When exact candidates must be locked but the tool does not support it, stop and disclose it; do not fabricate snapshot parameters. After submission, verify the sync record, the actual commit, and failed_items.
sync_link
do not use confirm_git_sync. To evolve the binding set:
| Intent | Toolchain |
|---|---|
| extra files in the repository, need to add cases | Phase 6 addFiles flow |
| files deleted from the repository, need to retire bindings | Phase 6 sourceDeleted flow |
| replay failed items in an existing sync record | testany_retry_git_sync({sync_record_id}) |
testany_preview_git_sync can still be called on sync_link, as an audit of "the current bound file's changes relative to last_synced_commit "—butit will not treat new files in the repository that have not yet been selected as candidates; new files are handled by the addFiles candidate list.
Failure Handling
SyncResult.failed_items If non-empty, report partial completion first; only when the current task has authorized retrying the same scope should you consider testany_retry_git_sync({sync_record_id}), and do not loop infinitely. Note:retry is only available for sync_link, and managed_import's confirm already handles per-file failures internally.
Phase 5: Switch (operates only on the sync_mode dimension)
the switch tool does not change import_mode(managed_import ↔ sync_link); it only operates on sync_mode.
Both are preview → confirm two-phase: preview first to gather evidence; use the Sync Authorization Decision Table to distinguish authorized submissions from differences that genuinely require a new decision. Version switching or tracking mode permissions cannot be derived from an ordinary sync permission.
switchCommit — change the pinned commit
- Preconditions:
sync_mode=pinned_commit(both managed_import and sync_link are fine) testany_preview_git_switch_commit({target_commit})→_confirm_git_switch_commit({target_commit, file_selections?})- sync_link's confirm derives
file_selectionsfrom preview.changes; managed_import's confirm does not acceptfile_selections(will return 400) - If preconditions are not met (for example, it is already latest), return
ERR_SWITCH_NOT_ALLOWED
switchMode — unpin and return to following the branch HEAD
- Semantics:
sync_mode: pinned_commit → latest。is not switching between managed_import ↔ sync_link - Preconditions:
sync_mode=pinned_commit - Already
latest: returnERR_SWITCH_MODE_UNCHANGED - Not pinned: return
ERR_SWITCH_NOT_ALLOWED - Payload:
- managed_import:
_confirm_git_switch_mode({})(passing file_selections will be rejected by the backend) - sync_link:
_confirm_git_switch_mode({file_selections?})(optional, derived from preview)
- managed_import:
Phase 6: Relationship Evolution (supported by both managed_import and sync_link)
This is sync_link'sonlybinding set evolution path under the current phase (managed_import's addFiles is merely an optional means parallel to confirm_git_sync).
Add Files (addFiles)
Files that exist in the repository but do not yet have a binding on Testany:
testany_get_git_add_files_summary—available=falsecannot go through; seeblocked_reasontestany_list_git_add_files_candidates— get thesnapshot_commitand the candidate list- Read the complete candidate set, and select only the files/metadata authorized for this round; new candidates do not automatically expand authorization
testany_confirm_git_add_files({selected_files, snapshot_commit})—idempotency_keyAutomatically generated by the MCPsnapshot_commitMust be passed back verbatim The value obtained during list; if the value does not match, the backend will reject it, preventing a stale candidate set from being persisted under a race condition
Source file deleted (sourceDeleted)
Deleted in the repository but with the binding still present on Testany: the same summary → list → confirm flow, but the confirm parameter is file_binding_ids(not file_path).
The impact of deletion/decommissioning must be within the permission scope and cannot be derived from script synchronization. Submit according to the snapshot_commit from this round's list, and after submission verify the binding and the actual processing result.
Phase 7: Sync Audit
testany_list_git_sync_records({import_history_id, page?, per_page?})testany_get_git_sync_record({record_id, detail_page?, detail_per_page?})Go through the per-file results
Phase 8:Webhook
| Action | Tool |
|---|---|
| View configuration | testany_get_git_webhook_config |
| Enable / change track_scope | testany_update_git_webhook_config({webhook_enabled: true, track_scope?}) |
| Disable | testany_disable_git_webhook |
| Rotate secret | testany_regenerate_git_webhook_secret |
Key discipline:webhook_secret Onlyfirst-time enablementorjust regenerated is plaintext in the response; afterwards reads are all masked. Once plaintext is obtained:
- Immediately have the user enter this secret in the Git platform hook configuration
- At the same time, hand
platform_setup_guide.stepsover to the user in full - Have the user trigger a test event and observe
webhook_statusfrompending_verification→verified
Things that must be reminded to the user
- OAuth must be completed in a browser: the agent cannot click authorization on the user's behalf; after returning
authorize_url, it must clearly inform them and take on the polling status.initiate_git_oauthThefrontend_return_urirequired(absolute URL), strictly validated by the backend - webhook_secret appears only once: after first-time enablement or regeneration, it must be recorded immediately and synchronized to the Git side; otherwise the only option is to rotate it again
- addFiles / sourceDeleted depend on
snapshot_commit(both managed_import and sync_link): the value obtained during the list phase is passed back verbatim to confirm - sync_link does not go through this under the current phase
confirm_git_sync: the backend returns 422. To evolve the binding set, use addFiles / sourceDeleted - Deleting a connection does not cascade-delete import history: that import history will become
not_ready, and reauthorization is required - Gather evidence first, authorize according to impact: sync / switch uses this round's complete preview, relation uses the candidate snapshot; if the existing permission covers the actual difference, do not ask again; for out-of-bounds/deletion/drift, follow the synchronized authorization decision table stop the corresponding submission
- Do not make the user choose a workspace unless necessary: the top-level
workspace_keyis an optional compatibility field, not a required UX item. Ask only when the user explicitly wants to create a restricted case and the per-fileworkspace_keysis not provided - Do not assume
deployment_type: before allowing a global case, usetenantClient.getCreditStatusor confirm the environment type with the user;deployment_type=2under the environment, global will be directly rejected by the backend (E400001) - Do not interrogate the language when choosing a Runtime: directly list the
testany_filter_case_runtimesresults for the user to choose from, with the default recommendation beingCloudPrime-Default - Do not manually pass
idempotency_key: MCP automatically generates UUIDs internally in preview_sync / confirm_sync / retry_sync / confirm_add_files / confirm_source_deleted; passing an old-style UUID only adds an unnecessary step of code at the upper layer. Only pass a valid UUID explicitly when you clearly need deduplication across multiple calls installation_idmust be selected from the connection:connection.installation_bindings[*].installation_id, and the corresponding binding'saccount_loginmust match therepo_full_nameowner, otherwise 400
Common Questions
| User Question | Handling |
|---|---|
| "Why wasn't the new file synced?" | testany_get_git_import Check remote_latest_commit vs last_synced_commit; in managed mode also check the addFiles summary |
| "Can a sync be rolled back?" | V2 does not support rollback; the closest option is to use switchCommit to return to the old commit |
| "Which import created these cases?" | testany_list_git_import_file_bindings in test_case_key reverse lookup |
| "Webhook isn't receiving events" | Check webhook_status(pending_verification / error)+ platform_setup_guide |
| "The connection became not_ready" | Read not_ready_reason, use reauthorize or refreshScope to recover |
Reference
- Detailed payload conventions and error codes
- MCP schema resource:
testany://schema/import-git(enums, types, flowcharts, tool categorization) - Testany automation object model