You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@magpie.apache.org by po...@apache.org on 2026/09/18 17:48:49 UTC

(magpie) branch main updated: fix(vetted-ops): repo-tree must not answer with a truncated tree (#1282)

This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/magpie.git


The following commit(s) were added to refs/heads/main by this push:
     new 237fa1d8 fix(vetted-ops): repo-tree must not answer with a truncated tree (#1282)
237fa1d8 is described below

commit 237fa1d86ada3d11c6e5f605c2d2fdc207b5b9f7
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Fri Sep 18 19:48:44 2026 +0200

    fix(vetted-ops): repo-tree must not answer with a truncated tree (#1282)
    
    `repo-tree` asks for a recursive git tree and projects the response
    straight to `.tree[].path`. On a large repository the GitHub API answers
    `recursive=1` with `truncated: true` and a *partial* `tree`, and that
    projection discards the flag — the caller gets a short listing that is
    indistinguishable from a complete one.
    
    The failure is silent and the output is plausible, which is the worst
    shape a read operation can fail in. Observed on a private archive: the
    operation returned 56,981 paths and every entry under one top-level
    directory was simply absent, so a sweep built on it concluded that
    directory did not exist.
    
    Surface the truncation as a jq `error(...)` instead. `gh` then exits
    non-zero and the dispatcher reports exit code 4 (the underlying command
    failed) rather than success with half a tree. A complete response is
    unaffected and still projects to plain paths.
    
    Generated-by: Claude Opus 5
---
 tools/vetted-ops/src/vetted_ops/ops.py    |  9 ++++++++-
 tools/vetted-ops/tests/test_vetted_ops.py | 23 +++++++++++++++++++++++
 2 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/tools/vetted-ops/src/vetted_ops/ops.py b/tools/vetted-ops/src/vetted_ops/ops.py
index 356e3c23..67132e7d 100644
--- a/tools/vetted-ops/src/vetted_ops/ops.py
+++ b/tools/vetted-ops/src/vetted_ops/ops.py
@@ -1650,7 +1650,14 @@ _register(
             "-f",
             "recursive=1",
             "--jq",
-            ".tree[].path",
+            # A recursive tree over a large repository comes back with
+            # `truncated: true` and a partial `tree`. Projecting straight to
+            # paths drops that flag, so a caller cannot tell a complete listing
+            # from a partial one and reads the gap as "those paths do not
+            # exist". Fail loudly instead of answering with half a tree.
+            'if .truncated then error("repo-tree: the GitHub API truncated '
+            "this tree; the listing is incomplete. Walk the tree level by "
+            'level instead of relying on recursive=1.") else .tree[].path end',
         ],
     )
 )
diff --git a/tools/vetted-ops/tests/test_vetted_ops.py b/tools/vetted-ops/tests/test_vetted_ops.py
index d6452ce0..01844e4f 100644
--- a/tools/vetted-ops/tests/test_vetted_ops.py
+++ b/tools/vetted-ops/tests/test_vetted_ops.py
@@ -372,6 +372,29 @@ def test_no_read_operation_sends_fields_without_an_explicit_get(policy: config.C
             assert argv[argv.index("-X") + 1] == "GET", f"{name} sends fields with a non-GET method"
 
 
+def test_repo_tree_refuses_to_report_a_truncated_listing(policy: config.Config) -> None:
+    """
+    `gh api .../git/trees/<ref>?recursive=1` answers a large repository with
+    `truncated: true` and a *partial* `tree`. Projecting straight to
+    `.tree[].path` discards that flag, so the caller receives a short listing
+    that is indistinguishable from a complete one — and concludes that whole
+    directories do not exist.
+
+    That is the worst shape a read operation can fail in: silently, with
+    plausible output. The builder must surface the truncation as an error so the
+    dispatcher exits non-zero (code 4) instead of answering with half a tree.
+    """
+    op = ops.resolve("repo-tree")
+    params, _ = cli._validate_params(op, ["main"], policy)
+    argv = cli.build_argv(op, params, policy)
+
+    jq = argv[argv.index("--jq") + 1]
+    assert ".truncated" in jq, "repo-tree drops the API's truncation flag"
+    assert "error(" in jq, "repo-tree must fail loudly on a truncated tree"
+    # The complete-tree path still projects to plain paths.
+    assert ".tree[].path" in jq
+
+
 def test_board_add_item_takes_a_content_node_id(policy: config.Config) -> None:
     """
     Adding an issue to the board keys on the *content* node id, not a project