Wang1rrr opened a new issue, #4778:
URL: https://github.com/apache/rocketmq-dashboard/issues/4778

   ## Summary
   
   `rmqctl message query` and `message query-by-topic` hide the response's 
completeness metadata in their default table output whenever `items` is 
nonempty. An incomplete result and a complete result with the same rows produce 
identical stdout and no warning on stderr. JSON output preserves the metadata.
   
   ## Environment
   
   - `rocketmq-studio`, CLI source at 
`4c697f07acde460e2344375cb1f82669f5b270fd`.
   - Windows amd64, Go 1.27.1; unmodified production CLI.
   - Synthetic loopback HTTP response shaped to the current tool schema; no 
live Studio backend or RocketMQ broker.
   
   ## Reproduce
   
   Build the CLI from the revision above (`cd rmqctl; go build -o 
rmqctl-repro.exe .`). Save the following as `repro.py`, then run `python 
repro.py ./rmqctl-repro.exe` with Python 3.9+. It uses isolated dummy 
credentials, temporary local config, and an ephemeral 127.0.0.1 server. Nothing 
connects to a real account or instance.
   
   The server sends the normal response envelope:
   
   ```json
   
{"code":200,"message":"success","data":{"items":[{"msgId":"mock-message-1","topic":"mock-topic","storeTime":1,"size":1}],"resultMayBeTruncated":true,"skippedCount":5}}
   ```
   
   <details>
   <summary>Self-contained production CLI reproducer</summary>
   
   ```python
   """Observe current CLI table metadata loss, using only a temporary loopback 
server."""
   import json
   import os
   from pathlib import Path
   import subprocess
   import sys
   import tempfile
   import threading
   from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
   
   cli = str(Path(sys.argv[1]).resolve())
   item = {"msgId": "mock-message-1", "topic": "mock-topic", "storeTime": 1, 
"size": 1}
   payload = {"items": [item], "resultMayBeTruncated": True, "skippedCount": 5}
   requests = []
   
   class Handler(BaseHTTPRequestHandler):
       def do_POST(self):
           request = 
json.loads(self.rfile.read(int(self.headers["Content-Length"])))
           expected = {"instanceId": "mock-instance", "topicName": 
"mock-topic", "limit": 1}
           if request.get("name") == "rmq.message.query":
               expected["key"] = "mock-key"
           valid = (self.client_address[0] == "127.0.0.1" and self.path == 
"/api/mcp/tools/call"
                    and request.get("name") in ("rmq.message.query", 
"rmq.message.query_by_topic")
                    and request.get("arguments") == expected)
           if not valid:
               self.send_error(400, "unexpected synthetic request")
               return
           requests.append(request)
           body = json.dumps({"code": 200, "message": "success", "data": 
payload}).encode()
           self.send_response(200)
           self.send_header("Content-Type", "application/json")
           self.send_header("Content-Length", str(len(body)))
           self.end_headers()
           self.wfile.write(body)
       def log_message(self, *_args):
           pass
   
   server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
   server.daemon_threads = True
   thread = threading.Thread(target=server.serve_forever, daemon=True)
   thread.start()
   env = {"SYSTEMROOT": os.environ.get("SYSTEMROOT", r"C:\Windows"),
          "WINDIR": os.environ.get("WINDIR", r"C:\Windows"), "NO_PROXY": 
"127.0.0.1,localhost",
          "REPRO_AK": "test-ak", "REPRO_SK": "test-sk"}
   try:
       with tempfile.TemporaryDirectory(prefix="rmqctl-repro-", dir=Path.cwd()) 
as tmp:
           assert Path(tmp).resolve().is_relative_to(Path.cwd().resolve())
           config = Path(tmp) / "config.yaml"
           config.write_text("currentContext: repro\ncontexts:\n  repro:\n"
                             f"    server: 
http://127.0.0.1:{server.server_port}\n";
                             "    credential:\n      accessKeyRef: 
env:REPRO_AK\n"
                             "      secretKeyRef: env:REPRO_SK\n", 
encoding="utf-8")
           config.chmod(0o600)
           for verb in ("query", "query-by-topic"):
               outputs = []
               for partial, fmt in ((True, "table"), (False, "table"), (True, 
"json")):
                   payload.update(resultMayBeTruncated=partial, skippedCount=5 
if partial else 0)
                   args = [cli, "--config", str(config), "--instance-id", 
"mock-instance",
                           "--timeout", "5s", "message", verb, "--topic-name", 
"mock-topic", "--limit", "1"]
                   if verb == "query":
                       args += ["--key", "mock-key"]
                   if fmt != "table":
                       args += ["--output", fmt]
                   result = subprocess.run(args, env=env, capture_output=True, 
text=True, timeout=15)
                   assert result.returncode == 0, result.stderr
                   assert not result.stderr, result.stderr
                   outputs.append(result.stdout)
               assert "mock-message-1" in outputs[0]
               assert outputs[0] == outputs[1], "Partial and complete tables 
differ"
               assert json.loads(outputs[2]) == payload
               print(f"{verb}: partial and complete tables are identical; JSON 
preserves metadata")
               print(outputs[0], end="")
           assert len(requests) == 6
   finally:
       server.shutdown()
       server.server_close()
       thread.join(timeout=3)
   ```
   
   </details>
   
   For both commands, the partial (`true/5`) and complete (`false/0`) fixtures 
print the same table:
   
   ```text
   MSGID           SIZE  STORETIME  TOPIC
   mock-message-1  1     1          mock-topic
   ```
   
   All six CLI calls exit 0 with empty stderr. The JSON control preserves 
`resultMayBeTruncated: true` and `skippedCount: 5`.
   
   A separate expanded diagnostic also confirmed that nonempty `true/0` 
provider-bounded results are indistinguishable from complete results. Empty 
`false/0` and `true/0` responses instead use the current JSON fallback and 
preserve the fields. These are schema-shaped controls, not captured provider 
responses. YAML preserves the two completeness fields; no claim of full YAML 
semantic validation is intended.
   
   ## Expected behavior
   
   The default human-readable table should expose when the result may be 
incomplete, while preserving the existing rows and structured JSON/YAML 
contract. `skippedCount` is only the number omitted from the provider-bounded 
result, not necessarily the total number of all unseen broker messages. In 
particular, `resultMayBeTruncated=true` with zero skipped rows must not look 
complete.
   
   ## Root cause and scope
   
   `cmd/catalog.go` routes L1 table output to `renderTable`. 
`cmd/catalog_renderers.go` extracts the `items` field and renders only those 
rows. Nonempty responses discard sibling metadata; empty responses serialize 
the original object.
   
   The current `MessageQueryOutput` contract already supplies the completeness 
fields for both message query tools. This observation does not require changes 
to limits, provider queries, or the server contract.
   
   ## Related work and proposed discussion
   
   [#4645](https://github.com/apache/rocketmq-dashboard/pull/4645) and 
[#4646](https://github.com/apache/rocketmq-dashboard/issues/4646) address 
generating truncation information in an older server output contract. This 
report concerns the CLI dropping information that is already present in the 
current response. Those proposals should be considered together when reviewing 
overlap.
   
   I plan a narrow CLI fix: emit a warning on stderr only for these two query 
tools when the default table contains nonempty rows and resultMayBeTruncated is 
true. Keep stdout rows, structured JSON/YAML, empty-result fallback, query 
parameters and exit semantics unchanged. This makes incompleteness visible 
without redesigning tables or adding a dependency. The new stderr diagnostic is 
an intentional observable change; scripts that equate any stderr output with 
failure may need to distinguish warnings. No implementation has been made at 
the time of this report, and feedback on this scope is welcome.
   
   ## Verification limits
   
   The two command paths were exercised through actual production CLI processes 
with synthetic HTTP fixtures and independently checked. This is a 
client-rendering observation, not Java/Broker end-to-end verification or a 
completed fix. AI assistance was used for analysis and reproducer preparation; 
outputs and scope were independently reviewed.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to