Shim Trace Capture
Added src/gh_forgejo_shim/trace.py and wired routing.run_gh() to append route, host/repo, duration, exit code, env keys, and output-size records when tracing is enabled.
The shim can now record opt-in gh and temporary git probe traces, summarize those JSONL files, and run a local Codex-style smoke sequence without changing default runtime behavior.
Implemented a diagnostic-first loop for the reported Codex.app states: CLI availability, PR status availability, and branch selector freshness. Tracing is enabled only by FJ_SHIM_TRACE, body excerpts require FJ_SHIM_TRACE_BODY=1, and auth-sensitive output is redacted or suppressed.
Added src/gh_forgejo_shim/trace.py and wired routing.run_gh() to append route, host/repo, duration, exit code, env keys, and output-size records when tracing is enabled.
Added gfj trace summarize <trace.jsonl> to group failures, slow calls, unsupported commands, routes, and known Codex probe patterns.
Added gfj trace smoke, which runs the observed read-only gh and raw git probe family and reports exit codes, timings, and output sizes.
Added gfj trace git-recorder create/remove for one-session raw git tracing when branch UI behavior bypasses the gh shim.
Previous fixes added Forgejo-shaped support for gh auth status, gh api user, gh pr status, and Codex PR board fields, but the next compatibility step needed exact observations instead of more guessing. This change turns live Codex subprocess behavior into replayable records and focused tests.
run_gh() remains a no-op for tracing unless FJ_SHIM_TRACE is present.gh calls use a traced subprocess path only when tracing is enabled; the normal delegate path still calls the existing run_program().git recorder prepends one wrapper directory to PATH, streams real git output, records JSONL, and removes the directory with gfj trace git-recorder remove.README.md and docs/dev/testing.html so future Codex diagnosis starts from traces and smoke checks.Focused unified diff excerpt. It shows the trace hook, CLI surface, recorder creation, and documentation entry without embedding full test hunks or generated trace data.
diff --git a/src/gh_forgejo_shim/routing.py b/src/gh_forgejo_shim/routing.py
@@
+from .trace import append_trace, build_record, capture_streams, suppress_stdout_body, tracing_enabled
@@
def run_gh(
argv: list[str],
@@
+ traced = tracing_enabled(values)
+ out, err = (
+ capture_streams(
+ stdout=stdout,
+ stderr=stderr,
+ env=values,
+ redact_stdout_body=suppress_stdout_body(argv),
+ )
+ if traced
+ else (stdout or sys.stdout, stderr or sys.stderr)
+ )
+ started = time.perf_counter()
@@
+ finally:
+ if traced:
+ append_trace(
+ build_record(
+ kind="gh",
+ argv=argv,
+ cwd=cwd,
+ env=values,
+ duration_ms=(time.perf_counter() - started) * 1000,
+ exit_code=exit_code,
+ stdout_summary=out.summary(),
+ stderr_summary=err.summary(),
+ route=_trace_route(decision),
+ host=_trace_host(decision),
+ repo=_trace_repo(decision),
+ ),
+ env=values,
+ )
diff --git a/src/gh_forgejo_shim/cli.py b/src/gh_forgejo_shim/cli.py
@@
+ trace = subparsers.add_parser("trace", help="capture and summarize Codex gh/git diagnostics")
+ trace_subparsers = trace.add_subparsers(dest="trace_command", required=True)
+ summarize = trace_subparsers.add_parser("summarize", help="summarize a shim trace JSONL file")
+ summarize.add_argument("trace_path")
+ smoke = trace_subparsers.add_parser("smoke", help="run the local Codex gh/git probe smoke checks")
+ git_recorder = trace_subparsers.add_parser("git-recorder", help="generate or remove a temporary git recorder wrapper")
diff --git a/src/gh_forgejo_shim/git_recorder.py b/src/gh_forgejo_shim/git_recorder.py
@@
+TRACE_PATH_ENV = "FJ_SHIM_TRACE"
+REAL_GIT_ENV = "FJ_SHIM_REAL_GIT"
@@
+def create_git_recorder(
+ trace_path: str | Path,
+ *,
+ real_git: str | Path | None = None,
+ root_dir: str | Path | None = None,
+ wrapper_dir: str | Path | None = None,
+ env: Mapping[str, str] | None = None,
+) -> GitRecorder:
+ """Create a temporary ``git`` executable that records JSONL invocations."""
diff --git a/README.md b/README.md
@@
+## Codex Probe Diagnostics
+
+When Codex.app reports `GitHub CLI unavailable`, `Pull request status unavailable`, or stale branch data, enable tracing before guessing at the fix.
+
+```sh
+export FJ_SHIM_TRACE="$PWD/codex-probe-trace.jsonl"
+export FJ_SHIM_TRACE_BODY=1
+gfj trace summarize codex-probe-trace.jsonl
+```
Users and future agents can capture the exact command that caused a Codex.app compatibility state, summarize the likely failure class, and replay the local probe family before changing shim behavior. The default installed shim remains unchanged until tracing is explicitly enabled.
python3 -m unittest ran 119 tests.python3 -m compileall -q src tests.git diff --check.run_gh() records.gh calls capture subprocess output before forwarding it. This is opt-in only and keeps the existing streaming delegate path for normal runtime.FJ_SHIM_TRACE_BODY=1 is set. Tokens and auth-sensitive patterns are redacted, and gh auth token stdout excerpts are suppressed.FJ_SHIM_TRACE during the next real Codex.app reproduction and convert any newly observed unsupported command shape into a focused replay test.