Helix CI/CD 故障排查MCP服务

helix.mcp 是一个面向 AI 代理排查 .NET CI/CD 失败的 CLI 与 MCP 服务器,通过精简日志返回、原位模式搜索和跨进程缓存,帮助用户在 Helix 与 Azure DevOps 上快速定位构建、测试及发布问题。

  • by lewing
  • C#
  • 混合部署
  • 4 星标
  • 更新于
在 GitHub 查看 lewing/helix.mcp

使用说明

helix.mcp

An increasingly inaccurately named CLI and MCP server for AI agents investigating .NET CI/CD failures across Helix and Azure DevOps.

No MCP needed. hlx works as a standalone CLI — dnx lewing.helix.mcp <command> (zero-install) or dotnet tool install -g lewing.helix.mcp. See the CLI reference.

Built with Squad — meet the squad.

Why?

AI agents investigating CI failures in dotnet repos (runtime, sdk, aspnetcore, etc.) hit two problems:

  1. Raw APIs return too much data. A single Helix console log can be megabytes. The Azure DevOps build timeline returns hundreds of records. Dumping this into a context window wastes tokens and drowns the signal.
  2. Every agent starts from scratch. When multiple agents — or the same agent across tool calls — inspect the same build, each one repeats the same API calls and downloads.

hlx solves both:

  • Return less, return better. Tools like helix_search and azdo_search_log search in place and return only matching lines with context — agents never download a full log. helix_status returns structured failure summaries instead of raw JSON. Default tail limits (500 lines), filter parameters (failed by default), and maxMatches caps keep responses focused.
  • Cache everything, share across processes. A local SQLite cache sits between agents and the APIs. Different MCP server instances (one per IDE window/terminal) share the same cache, so the second agent to inspect a job gets instant results. Smart TTLs track job lifecycle — running jobs cache briefly (15–30s), completed jobs cache for hours.

Zero config — public dotnet CI works out of the box. Install and go.

Context-Efficient Design

Every tool is designed to minimize token consumption in agent context windows:

Technique How it helps
Tail limits helix_logs and azdo_log return the last N lines (default 500), not the full log
Complete collection on demand CLI list commands such as azdo test-runs --all and azdo test-results --all populate complete-list cache keys for deterministic scanner snapshots while MCP defaults stay capped
Pattern search helix_search and azdo_search_log search outside agent context and return matching lines with configurable context — no full ingestion
Failure-first defaults helix_status, azdo_timeline, azdo_test_results default to showing only failures
Structured JSON Failure summaries, test results, and timeline data come pre-parsed — no agent-side text extraction
Batch operations helix_batch_status checks up to 50 jobs in one call; helix_find_files scans N work items instead of N+1 API calls
Ranked search azdo_search_log can search all build logs, ranking by failure likelihood and stopping early when maxMatches is reached
Idempotent annotations Read-only tools marked safe for retry and caching — clients can optimize scheduling and error recovery

Investigation Path

  • When repo workflows vary, start with helix_ci_guide(repo) for the repo-specific path and search patterns.
  • Use helix_parse_uploaded_trx only when the work item uploads structured test results to Helix (runtime CoreCLR, XHarness device tests).
  • Otherwise use azdo_test_runs → azdo_test_results for structured results, or helix_search when the signal is in console output.

Artifact bytes, binlogs, and attachments

azdo_artifacts and azdo_test_attachments list metadata. Use azdo_download_artifacts / azdo_download_test_attachments (MCP), or:

hlx azdo download-artifacts 1621192 \
  --artifact-name Logs_Build_Attempt1_browser_wasm_linux_Release_LibraryTestsCoreCLR \
  --member-path Release/Build.binlog --output-dir ./evidence --json
hlx azdo download-test-attachments 98765 --result-id 1234 \
  --pattern '*.dmp' --output-dir ./evidence --json
hlx azdo download-test-attachments 98765 --all --delivery file --json

Exact artifact member paths use native file transfer (including PipelineArtifact's leading-slash subPath); member globs spool the archive to private disk and retain only matched decoded files and a covering catalog. Without member selectors, the requested original archive/binary is preserved. **/Build.binlog also matches root members; */Build.binlog requires a directory.

Explicit selectors are not restricted to failed Logs or a content classification. There is no inferred 50/512 MiB per-object ceiling. Optional caller planning bounds can be waived with --allow-large; physical storage, authentication, unsafe paths, and integrity checks remain real protections. Progress goes to stderr; JSON contains SHA-256, server-local paths, bounded report windows, and a verified full-report delivery reference. Continue through hlx_read_evidence, or hlx evidence read <evidence-id> --encoding base64 --offset-bytes 0 --length-bytes 2048. Use CLI on the debugger host or a deliberately shared filesystem for binaries: there is no public transfer endpoint, automatic upload, dump debugging, or binlog event parsing.

Collection stays metadata-only unless byte flags are supplied: --download-azdo-artifacts <glob>, --download-artifact-members <glob>, and --download-test-attachments <glob>. Explicit artifact-name globs select that set, not an implicit failed-only subset. Automatic --artifact-byte-scope failed-logs prefers failed-leg Logs/Build.binlog. Unattended retained/streamed byte planning defaults to 2 GiB; --allow-large resumes the original scope. Verified files survive retries; interrupted upstream files restart without Range, while local evidence reads always support exact byte offsets. Selected byte/cache descriptor pairs export in the existing cache snapshot; metadata-only older snapshots cannot supply missing bytes. Recollect in live mode into a new snapshot rather than modifying the old one.

Current scope is not full collector-design compliance: broad test-result and attachment-volume recovery repairs remain follow-up work. ZIP bodies spool to disk, but member catalogs and full download-report serialization still materialize in memory; bounded inline windows do not imply bounded-memory acquisition.

Cross-Process Caching

 IDE / Agent processes          hlx stdio MCP servers
┌─────────────┐               ┌──────────────┐
│  VS Code    │──────────────▶│  hlx pid 1   │──┐
│  Copilot CLI│──────────────▶│  hlx pid 2   │──┤
│  Other agent│──────────────▶│  hlx pid 3   │──┤
└─────────────┘               └──────────────┘  │
                                                 ▼
                                          ┌─────────────┐    miss    ┌─────────────────┐
                                          │ Cache Layer  │──────────▶│ Helix / AzDO API│
                                          │  Cache hit?  │◀──────────│                 │
                                          └──────┬───────┘  response └─────────────────┘
                                            hit  │
                                                 ▼
                                     ┌───────────────────────┐
                                     │  SQLite + Disk shared  │
                                     │  ┌─────────┬────────┐ │
                                     │  │SQLite DB │Artifact│ │
                                     │  │WAL mode  │ files  │ │
                                     │  └─────────┴────────┘ │
                                     │                       │
                                     │  Auth isolation:       │
                                     │  cache-a1b2c3/         │
                                     │  cache-d4e5f6/         │
                                     │  public/               │
                                     └───────────────────────┘
  • SQLite WAL mode — multiple processes read/write the same DB safely with busy timeout
  • Smart TTLs — running jobs: 15–30s, completed jobs: 1–4h, console logs for running jobs: never cached. AzDO in-progress builds use dual-key freshness with incremental log fetching (delta-append)
  • Auth-isolated storage — each unique token gets its own cache directory (cache-{hash}/). Unauthenticated requests use public/. No cross-token leakage
  • LRU eviction — 1 GB cap (configurable via HLX_CACHE_MAX_SIZE_MB), artifact files expire after 7 days without access
Setting Default Env var
Max cache size 1 GB HLX_CACHE_MAX_SIZE_MB (set to 0 to disable)
Cache location (Windows) %LOCALAPPDATA%\hlx\ —
Cache location (Linux/macOS) $XDG_CACHE_HOME/hlx/ —
hlx cache status   # Show cache size, entry count, oldest/newest entries
hlx cache clear    # Wipe all cached data

Delivered evidence retention

Oversized MCP responses delivered as files live inside the existing per-user cache trust boundary: <effective-cache-root>/delivery/<credential-partition>/. The effective root already isolates live auth contexts. Offline replay never writes into its immutable snapshot: delivery goes to <normal-user-cache-root>/eval-delivery/<SHA256-of-normalized-snapshot-path>/<credential-partition>/. The normal user root is %LOCALAPPDATA%/hlx on Windows or $XDG_CACHE_HOME/hlx (default ~/.cache/hlx) on Unix; XDG_RUNTIME_DIR and HLX_DELIVERY_RUNTIME_DIR are not used. If that root overlaps the snapshot in either direction or delivery cannot write there, replay uses <LocalApplicationData>/hlx/delivery-ephemeral/<snapshot-hash>/<credential-partition>/, falling back to ~/.hlx/delivery-ephemeral/<snapshot-hash>/<credential-partition>/ if needed. Fallbacks must be writable and outside the snapshot. Overlap checks normalize full paths, resolve existing directory symlinks, and ignore case on Windows/macOS.

Each delivery partition has a separate soft sub-budget of 25% of HLX_CACHE_MAX_SIZE_MB (minimum one byte), tracked in delivery.db rather than included in hlx cache status artifact bytes. Every put, dedupe hit, descriptor lookup, and read renews a 24-hour pin. Expired unpinned entries follow the cache's artifact-age limit and oldest-access-first size eviction. Pins always win over the soft cap; FileEvidenceDeliveryStore.TryGetRetentionStatus reports pinned bytes and any overage. SQLite transactions serialize pins and eviction across processes.

With HLX_CACHE_MAX_SIZE_MB=0, file delivery still works under <base-cache-root>/delivery-ephemeral/<credential-partition>/, with the same pins and retention and a 256 MiB soft budget. An unwritable root uses the same external per-user fallback folders (without a snapshot hash). Delivery is reconstructable evidence, not part of snapshot exports; hlx cache clear clears the primary cache, not delivery evidence protected by these pins. Delivery follows the cache's trust model without separate owner/ACL checks. New Unix directories/files are created with modes 0700/0600 as defense in depth.

Offline Snapshots

Export the current cache as a portable snapshot for offline replay, testing, or reproducible scenarios:

hlx snapshot export /tmp/my-snapshot         # Create snapshot
hlx snapshot validate /tmp/my-snapshot       # Verify integrity
HLX_EVAL_SNAPSHOT=/tmp/my-snapshot hlx status <jobId>  # Use snapshot

Snapshots preserve all cached data and artifact files. When HLX_EVAL_SNAPSHOT is set, hlx runs entirely offline against the snapshot's SQLite database, including the non-secret AzDO cache partition recorded during collection; replay does not need AZDO_TOKEN or az login. For scanner bundles, prefer collecting in CI with hlx collect azdo-build --export, upload the snapshot, and investigate anywhere from the complete keys populated during collection. See the CLI reference for partition selection and the complete workflow.

Schema v2 snapshots also preserve deterministic acquisition failures (not_found, access_denied, invalid_response) recorded during live collection. Offline replay returns the original provider failure with source: "snapshot", replayed: true, and recordedAt; keys never collected into the snapshot return kind: "not_in_snapshot", provider: "cache".

Snapshots produced by v0.10.3 or earlier can contain empty/corrupt raw AzDO logs from NUL-containing cache values; run hlx cache clear, re-collect the snapshot, and verify with hlx snapshot validate.

Collecting scanner snapshots

Use hlx collect azdo-build to populate a deterministic cache, export it, and write a manifest in one step:

hlx collect azdo-build "$BUILD_ID_OR_URL" --export /tmp/my-snapshot
HLX_EVAL_SNAPSHOT=/tmp/my-snapshot hlx mcp

The exported manifest lives at manifest/hlx-collect-manifest.json; read it to distinguish complete snapshots from declared gaps before handing the snapshot to an offline scanner. See the scanner workflow and collect command reference.

Explicit --test-scope all streams every supported result outcome through cache-backed pages, including builds above 10,000 results. It does not request passed-result attachments: diagnostic attachment selection remains the default. Explicit --test-attachment-scope all is independently honored without a numeric unlock. Unattended attachment fan-out can defer with an exact same-request --allow-large --resume command. Progress and acquisition heartbeats go to stderr; JSON stdout stays parseable.

Query-backed build sets

hlx azdo builds / azdo_builds discover builds by pipeline exact name/glob (duplicate names form a union), build number/glob, required tags, result/reason, repository, PR/branch and queue/start/finish time. Full fields and the interactive top=20 default are preserved; --projection compact is opt-in. CLI JSON adds source completeness, exact counts when known, cache provenance and stable viewId continuation. Complete sets require --all (MCP all=true); --output, file delivery and hlx_read_evidence are optional ways to deliver them, and on their own carry only the requested page; interactive queries have no collector quota.

hlx azdo builds --definition-name '*wasm*' --result failed --projection compact --json
hlx azdo builds --pr-number 12345 --repository-id dotnet/runtime --repository-type GitHub --all --output builds.json
hlx collect azdo-builds --query build-query.json --cache-dir ./build-cache --export ./build-snapshot --json

The strict version-1 query JSON contains semantic filters, not paging/output settings. Build-set collection freezes discovery before member evidence, persists one schema-v2 manifest, then exports one existing-format snapshot. Metadata-only scope is available with --build-scope metadata. Complete query evidence replays credential-free at different limits/projections and supports provably narrower queries (for example, failed-only from an all-result PR set); uncovered scope returns not_in_snapshot with an exact live recollection/new-snapshot action.

The unattended defaults—50 builds, 25 definitions, 5,000 scanned builds, 10,000 run-wide test results and 1,000 attachment requests—are planning safeguards, not access gates. Explicit larger allowances are honored; --allow-large --resume continues the same captured query/checkpoint and waives logical thresholds, not auth, integrity or actual storage checks. See the CLI reference for query format and recovery.

Build-set resume preserves discovery checkpoints even after provider failures and reuses successful cached Helix file transfers. Result/attachment recovery commands retain the aggregate export context and select a fresh destination; existing snapshots are never overwritten.

MCP Tools

Helix Tools (9)

Tool Description
helix_status Job pass/fail summary with failure categorization. Filter: failed (default), passed, all.
helix_batch_status Status for up to 50 jobs at once with aggregate totals.
helix_logs Console log content (last N lines, default 500).
helix_search Search a console log or uploaded file for repo-specific failure patterns without downloading the full content.
helix_files List uploaded files for a work item, grouped by type.
helix_find_files Search across work items for files matching a glob (*.binlog, *.trx, *.dmp).
helix_work_item Detailed work item info (exit code, state, machine, duration, failure category).
helix_download Download files from a work item or direct blob URL. Supports glob patterns for work-item downloads.
helix_parse_uploaded_trx Parse TRX/xUnit XML files uploaded to Helix blob storage into test names, outcomes, and error messages.

AzDO Tools (12)

Tool Description
azdo_build Build details (status, result, branch, timing, URL). Accepts URLs or integer IDs.
azdo_builds Discover builds by pipeline name/glob, build number, tags, result/reason, repository, PR/branch and time; full/compact pages or verified file delivery.
azdo_timeline Triage an AzDO build timeline: errors/Helix-first Jobs/Tasks, short deduplicated issue previews (with counts), log IDs, and ancestor context. Defaults to projection=triage (at most 10 rows, Phase rows omitted); filter defaults to failed for ordinary triage, or all once an explicit recordId/parentId/type/result/state/name selector is given with no preset. Also supports projection=compact|full|summary, paging via offset/limit/viewId/next, and maxResponseBytes (default 12288, inline shaping target — not a data-access ceiling). Full/complete selections stay reachable via delivery=file/all and hlx_read_evidence, never refused.
azdo_log Log content for a specific build step (last N lines, default 500).
azdo_search_log Search a specific build log or all ranked build logs for a pattern with context lines.
azdo_search_timeline Search timeline records by name or issue pattern.
azdo_changes Commits/changes associated with a build.
azdo_test_runs Test run summaries (total, passed, failed counts).
azdo_test_results Individual test results. Filter by outcome (outcomes param). Defaults to failed tests only (top 200).
azdo_artifacts Build artifacts with pattern filtering (e.g., *.binlog).
azdo_test_attachments Test result attachments (screenshots, logs, dumps).
azdo_evidence_plan Plan failed/canceled job → artifact evidence mapping. auto (default) joins by source GUID, then falls back to normalized-exact names; source-ID-only, normalized-exact, and exact-name modes are also available. Parses selected arcade queue-monitor timeline issues into structured helixFailures[] rows (helixJobId, workItem, state, exitCode, sourceFormat, suggestedFetches). Paging fields: helixFailureOffset/Limit/Total/Truncated. Any partial Helix-failure page (helixFailureTotal > helixFailures.length, including later offset pages) is fail-closed: complete=false, truncated=true, helix_failures_truncated, and CLI exit 2. Read-only; returns ranked candidates, ambiguity and truncation metadata, completeness status, and stable incompleteDetails[].code values (never silently chooses). The MCP stripAttemptPrefix parameter defaults to true; the equivalent CLI strips by default and exposes the inverse bare flag --keep-attempt-prefix. suggestedFetches[].tool emits helix_work_item, helix_logs, and helix_files for drilldown.

azdo_evidence_plan incomplete codes: artifact_ambiguous = multiple artifact candidates for one selected job; artifact_missing = no matching artifact candidate; candidates_truncated = a job's candidate list exceeded the per-entry bound; entries_truncated = selected jobs exceeded the plan entry bound; helix_failures_truncated = the current response contains only a partial Helix-failure page; monitor_unparseable = a selected monitor-like job had no parseable Helix work-item failures; monitor_unresolved_job_id = failure-shaped monitor entries lacked a recoverable Helix job ID. Human output reports paging ranges such as showing 1-1 of 2; collectors should treat exit 2 from paging as "fetch/merge remaining pages" or rerun with a limit at least as large as helixFailureTotal.

azdo_timeline response shaping is access-first: maxResponseBytes (default 12288) is an inline shaping target, never a refusal — a row/record too large to inline is delivered as a verified evidence file/reference, readable with hlx_read_evidence, instead of being dropped or erroring. previewIssueLimit (default 5) and previewChars (default 200) bound the deduplicated issue preview per triage row; the full, untruncated text for any shortened preview is always reachable through the row's own recovery action. A per-call delivery="file"/all=true request retrieves the complete selected scope, ignoring that shaping target (there is no delivery="all" value). The CLI has an equivalent --delivery auto|inline|file|chunked flag; combine --all with --delivery file (or just --all) for the same complete-selection behavior (--output remains available as a CLI-specific escape hatch). Inline maxResponseBytes is clamped to an 8 KiB–16 KiB effective range (default 12288), independent of a smaller requested value; both the effective (clamped) maxResponseBytes and the caller's original requestedMaxResponseBytes are reported in the response.

Parameter validation: MCP tools reject unknown parameter names with a structured error (including a "Did you mean?" hint when the unknown name is close to a known parameter) — LLM-hallucinated or mistyped param names get immediate feedback instead of silent drops. Common aliases are resolved automatically before validation: buildId / build_id / buildUrl → buildIdOrUrl on AzDO tools that accept a build identifier; result → resultFilter on azdo_search_timeline.

Errors and exit codes

CLI JSON hard failures use { "ok": false, "error": { ... } }; MCP tool failures set isError: true and put the same object under structuredContent.error. error.kind is one of not_found, access_denied, rate_limited, timeout, transport_error, invalid_response, or not_in_snapshot; provider is azdo, helix, or cache; fields are kind, provider, operation, resource, optional httpStatus, optional retryAfterSeconds, optional snapshot replay fields (source, replayed, recordedAt), and message. Resource URL values redact query strings and fragments.

Exit codes are 0 for success (including genuinely empty successes), 1 for hard command/acquisition errors, and 2 when bounded output was written but is incomplete/truncated (for example an AzDO list JSON envelope with truncated=true, unless --allow-truncated is supplied, or an incomplete evidence plan). Callers own retry/skip policy. When a direct build-log body is empty, hlx validates the logId against the build's logs list and timeline record.log.id; if absent from both, it fails with kind=not_found. Empty full logs are not cached as successes. See the CLI reference for examples.

MCP Resources

MCP resources are URI-addressable data that clients can discover and read without invoking a tool.

Resource URI Description
ci://profiles Overview of CI investigation patterns for all .NET repositories.
ci://profiles/{repo} CI investigation guide for a specific .NET repository (e.g., ci://profiles/runtime).

These provide the same CI investigation guides available via the helix_ci_guide tool, exposed as client-discoverable resources for browsing and caching.

Authentication

Helix

No auth needed for public Helix jobs (dotnet open-source CI). For private jobs:

hlx login          # Opens browser, prompts for token, stores via git credential
hlx auth-status    # Check current auth status
hlx logout         # Remove stored token

Token resolution: HELIX_ACCESS_TOKEN env var → stored credential via git credential → error with helpful message.

Azure DevOps

The AzDO tools work anonymously against public projects (for example dnceng-public/public). For private orgs (for example devdiv/DevDiv), authentication is required.

Credential chain (tried in order):

  1. AZDO_TOKEN environment variable — both PATs and Entra access tokens are auto-detected:
    • PATs (Personal Access Tokens): sent as Basic auth. Needs Build(read) + Test(read) scopes.
    • Entra/JWT tokens: sent as Bearer auth.
  2. Azure CLI credential (AzureCliCredential from Azure.Identity) — uses your az login session. Works for any org your Azure identity has access to.
  3. az CLI subprocess — falls back to az account get-access-token if Azure.Identity isn't available.
  4. Anonymous — no auth header. Works for public projects only.

When auth fails, the error tells you what was tried and how to fix it:

Can't access devdiv/DevDiv — authentication required (401).

Current auth: anonymous (no credentials found)

To resolve:
• Run 'az login' (if your Azure identity has access to this org)
• Set AZDO_TOKEN to a Personal Access Token with Build(read) + Test(read) scopes

Pass tokens via MCP config:

{
  "servers": {
    "hlx": {
      "type": "stdio",
      "command": "dotnet",
      "args": ["dnx", "--yes", "lewing.helix.mcp"],
      "env": {
        "HELIX_ACCESS_TOKEN": "your-helix-token",
        "AZDO_TOKEN": "your-azdo-pat-or-entra-token"
      }
    }
  }
}

The HTTP MCP server supports per-request auth via Authorization: Bearer <token> headers, with isolated cache per client. Set HLX_API_KEY to gate server access.

Installation

Use with the dotnet-dnceng plugin (recommended)

The dotnet-dnceng plugin bundles hlx alongside related MCP servers (Azure DevOps, Maestro, binlog) and CI analysis skills — one install, batteries included:

copilot extensions install lewing/agent-plugins/plugins/dotnet-dnceng

This gives your agent helix_* and azdo_* tools plus skills for CI failure analysis, codeflow tracing, and dependency flow debugging.

Run with dnx (no install needed)

dnx lewing.helix.mcp

dnx (new in .NET 10) auto-downloads and runs NuGet tool packages. MCP mode is the default when no subcommand is given.

Install as global tool

dotnet tool install -g lewing.helix.mcp

After installation, hlx is available as a command. See the CLI reference for standalone usage.

Build from source

git clone https://github.com/lewing/helix.mcp.git
cd helix.mcp
dotnet build   # Requires .NET 10 SDK

The Microsoft.DotNet.Helix.Client package comes from the dotnet-eng feed. The included nuget.config references it.

MCP Configuration

Add to your MCP client config:

{
  "servers": {
    "hlx": {
      "type": "stdio",
      "command": "dotnet",
      "args": ["dnx", "--yes", "lewing.helix.mcp"]
    }
  }
}

If installed as a global tool, use "command": "hlx" with "args": [] instead.

Client Config file Top-level key
VS Code / GitHub Copilot .vscode/mcp.json servers
Claude Desktop (macOS) ~/Library/Application Support/Claude/claude_desktop_config.json mcpServers
Claude Desktop (Windows) %APPDATA%\Claude\claude_desktop_config.json mcpServers
Claude Code / Cursor .cursor/mcp.json mcpServers

VS Code uses servers. Claude Desktop, Claude Code, and Cursor use mcpServers — the rest is identical.

If MCP isn't working, the same data is available via hlx <command> directly — see the CLI reference.

For HTTP (remote/shared servers):

{
  "servers": {
    "hlx": {
      "type": "http",
      "url": "http://localhost:3001"
    }
  }
}

For MCP gateways that spawn stdio MCP servers as Docker subprocesses (e.g. GitHub Agentic Workflows's MCP Gateway), use the published container image:

# Recommended (pinned release):
mcp-servers:
  hlx:
    container: ghcr.io/lewing/helix.mcp:v0.8.0
    env:
      AZURE_DEVOPS_EXT_PAT: ""
      HELIX_ACCESS_TOKEN: ""

# Or, for following the latest release:
#   container: ghcr.io/lewing/helix.mcp:latest

Images are published on tagged releases (v*); the publish workflow also supports workflow_dispatch for backfilling images for an existing tag or publishing ad-hoc validation tags. Multi-arch: linux/amd64, linux/arm64.

Security

  • Safe XML parsing — DTD processing prohibited, XmlResolver disabled, 50 MB character limit (XXE/billion-laughs protection)
  • Path traversal protection — all cache/download paths sanitized and validated against designated roots
  • URL scheme validation — only HTTP/HTTPS accepted for downloads
  • File search toggle — set HLX_DISABLE_FILE_SEARCH=true to disable content search tools
  • Credential storage — tokens managed by OS keychain via git credential, never stored in plaintext
  • Cache sensitivity — cached CI logs may contain secrets; treat the cache directory as sensitive. Tokens are never cached — only an irreversible 8-char hash is used for directory isolation

Known Issues

  • File listing uses ListFiles endpoint — avoids a known bug in the Details endpoint where file URIs are broken for subdirectories and unicode filenames (dotnet/dnceng#6072).

Requirements

  • .NET 10 SDK

License

MIT