已合并
【fix】: 修复precommit的oat检查抢资源问题 #1052
zhangfands创建于 6月26日
【fix】: 修复precommit的oat检查抢资源问题 #1052
已合并
zhangfands创建于 6月26日
3 个文件变更+179-33
@@ -1,9 +1,14 @@
1repos:1repos:
2- - repo: https://github.com/pre-commit/mirrors-clang-format2+ - repo: https://gitcode.com/pre-commit-clang/mirrors-clang-format
3- rev: v16.0.03+ rev: v18.1.8
4 hooks:4 hooks:
5 - id: clang-format5 - id: clang-format
6- types_or: [c++, c]6+ files: \.(c|h|cpp|hpp|cc|hh|cxx|hxx)$
7+ args:
8+ - "--style={BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 120, BreakBeforeBraces: Allman}"
liu-wei
liu-weiliu-wei7月3日

🔴 High | --style={...} 不是 clang-format 合法语法

事实:本行写了 args: ["--style={BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 120, BreakBeforeBraces: Allman}"...]

clang-format --style 只接受

  • 预设名(LLVM / Google / Chromium / Mozilla / WebKit
  • 字符串 file(默认,使用最近 .clang-format)
  • YAML 文件路径

{...} 带花括号的写法不在支持列表中。clang-format 找不到有效样式后会:

  • 退到默认(Google preset 或 LLVM)
  • 跟项目根 .clang-format 定义的 BasedOnStyle: Google, AccessModifierOffset: -4, SortIncludes: false, BreakBeforeBraces: Custom 全部脱钩
  • pre-commit 钩子跟手动 clang-format 跑同文件会格式结果不一致

修复:删掉这行 args 里的 --style=...,让 clang-format 默认走 file 模式:

hooks:
  - id: clang-format
    files: \.(c|h|cpp|hpp|cc|hh|cxx|hxx)$
    exclude: ^build/|tests/third_party/

项目根 .clang-format 会被自动拾取,pre-commit 和本地 clang-format 结果一致。

likedislike
9+ - "--verbose"
10+ - "-i"
11+ exclude: ^build/|tests/third_party/
7 12 
8 # OAT compliance check (Open Source Audit Tool)13 # OAT compliance check (Open Source Audit Tool)
9 - repo: local14 - repo: local
MOAT.xml+9-6
@@ -1,10 +1,10 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<!-- Copyright (c) 2025 Huawei Technologies Co., Ltd.2<!-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
3- This program is free software, you can redistribute it and/or modify it under the terms and conditions of 3+ This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4 CANN Open Software License Agreement Version 2.0 (the "License").4 CANN Open Software License Agreement Version 2.0 (the "License").
5 Please refer to the License for details. You may not use this file except in compliance with the License.5 Please refer to the License for details. You may not use this file except in compliance with the License.
6- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, 6+ THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7- INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. 7+ INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8 See LICENSE in the root of the software repository for the full text of the License.8 See LICENSE in the root of the software repository for the full text of the License.
9-->9-->
10 10 
@@ -28,6 +28,9 @@
28 <!--filteritem type="filename" name="*.uvwxyz" desc="Describe the reason for filtering scan results"/-->28 <!--filteritem type="filename" name="*.uvwxyz" desc="Describe the reason for filtering scan results"/-->
29 <!--filteritem type="filepath" name="abcdefg/.*.uvwxyz" desc="Describe the reason for filtering scan results"/-->29 <!--filteritem type="filepath" name="abcdefg/.*.uvwxyz" desc="Describe the reason for filtering scan results"/-->
30 <!--filteritem type="filepath" name="projectroot/[a-zA-Z0-9]{20,}.sh" desc="Temp files"/-->30 <!--filteritem type="filepath" name="projectroot/[a-zA-Z0-9]{20,}.sh" desc="Temp files"/-->
31+ <!-- 排除 YAML 配置文件 -->
32+ <filteritem type="filename" name="*.yaml" desc="YAML config files"/>
33+ <filteritem type="filename" name="*.yml" desc="YAML config files"/>
31 </filefilter>34 </filefilter>
32 <filefilter name="copyrightPolicyFilter" desc="Filters for copyright header policies">35 <filefilter name="copyrightPolicyFilter" desc="Filters for copyright header policies">
33 <!--filteritem type="filename" name="*.uvwxyz" desc="Describe the reason for filtering scan results"/-->36 <!--filteritem type="filename" name="*.uvwxyz" desc="Describe the reason for filtering scan results"/-->
@@ -59,11 +62,11 @@
59 <licensematcherlist>62 <licensematcherlist>
60 <licensematcher name="cann License" desc="If the scanning result is InvalidLicense, you can define matching rules here. Note that quotation marks must be escaped.">63 <licensematcher name="cann License" desc="If the scanning result is InvalidLicense, you can define matching rules here. Note that quotation marks must be escaped.">
61 <licensetext name="64 <licensetext name="
62- This program is free software, you can redistribute it and/or modify it under the terms and conditions of 65+ This program is free software, you can redistribute it and/or modify it under the terms and conditions of
63 CANN Open Software License Agreement Version 2.0 (the &quot;License&quot;).66 CANN Open Software License Agreement Version 2.0 (the &quot;License&quot;).
64 Please refer to the License for details. You may not use this file except in compliance with the License.67 Please refer to the License for details. You may not use this file except in compliance with the License.
65- THIS SOFTWARE IS PROVIDED ON AN &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, 68+ THIS SOFTWARE IS PROVIDED ON AN &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
66- INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. 69+ INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
67 See LICENSE in the root of the software repository for the full text of the License.70 See LICENSE in the root of the software repository for the full text of the License.
68 " desc=""/>71 " desc=""/>
69 </licensematcher>72 </licensematcher>
@@ -48,12 +48,25 @@ if [ -z "$_PYTHON" ]; then
48fi48fi
49 49 
50# ---------------------------------------------------------------------------50# ---------------------------------------------------------------------------
51-# 1. Ensure oat-py is installed51+# 1. Ensure oat-py is installed (serialized via flock to prevent parallel pip conflicts)
52# ---------------------------------------------------------------------------52# ---------------------------------------------------------------------------
53_OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing")53_OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing")
54if [ "$_OAT_OK" != "ok" ]; then54if [ "$_OAT_OK" != "ok" ]; then
55 echo "[OAT] oat-py not found. Installing oat-py>=1.0.1 ..."55 echo "[OAT] oat-py not found. Installing oat-py>=1.0.1 ..."
56- "$_PYTHON" -m pip install --quiet "oat-py>=1.0.1"56+ _LOCK_FILE="/tmp/oat_pip_install.lock"
57+ # flock ensures only one concurrent invocation runs pip install at a time;
58+ # re-check inside the lock so processes that waited skip redundant installs
59+ if command -v flock >/dev/null 2>&1; then
liu-wei
liu-weiliu-wei7月3日

🔴 High | flock -w 120 9 超时后没有 exit,反而"previous did not complete"放过去抢资源

问题代码块

if ! flock -n 9; then
    echo "[OAT] Another OAT scan instance is running. Waiting..."
    flock -w 120 9                              # ← 超时返回非零
    if [ -n "$_DONE_MARKER" ] && [ -f "$_DONE_MARKER" ]; then
        echo "[OAT] Scan already completed by another instance. Skipping."
        exit 0
    fi
    echo "[OAT] Previous instance did not complete. Proceeding with scan..."
    # Fall through to run the scan below          # ← 此时**没拿到锁**!
fi

根因:脚本set -e,所以 flock -w 120 9 超时返回非零后,脚本继续往下走。"Proceeding with scan" 这条分支执行时当前进程根本没拿到锁,跟前面卡住的 instance 真正并发跑扫描——抢资源的根因没解决

触发条件:前一个 instance 真的卡死 > 2 分钟(罕见但可能——比如 oat 子进程 hang 在大仓库上,或者前一个 instance 等外部锁 / 慢 IO)。

修复

if ! flock -n 9; then
    echo "[OAT] Another OAT scan instance is running. Waiting..."
    if ! flock -w 120 9; then
        echo "[OAT] Timed out waiting for previous instance. Exiting with error."
        exit 1
    fi
    if [ -n "$_DONE_MARKER" ] && [ -f "$_DONE_MARKER" ]; then
        echo "[OAT] Scan already completed by another instance. Skipping."
        exit 0
    fi
    echo "[OAT] Previous instance did not complete. Proceeding with scan..."
fi
likedislike
60+ (
61+ flock -w 120 9
62+ _RECHECK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing")
63+ if [ "$_RECHECK" != "ok" ]; then
64+ "$_PYTHON" -m pip install --quiet "oat-py>=1.0.1"
65+ fi
66+ ) 9>"$_LOCK_FILE"
67+ else
68+ "$_PYTHON" -m pip install --quiet "oat-py>=1.0.1"
69+ fi
57 _OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing")70 _OAT_OK=$("$_PYTHON" -c "import importlib.util; print('ok' if importlib.util.find_spec('oat') else 'missing')" 2>/dev/null || echo "missing")
58 if [ "$_OAT_OK" != "ok" ]; then71 if [ "$_OAT_OK" != "ok" ]; then
59 echo "[OAT] [WARNING] Failed to install oat-py. Please run: pip install oat-py>=1.0.1"72 echo "[OAT] [WARNING] Failed to install oat-py. Please run: pip install oat-py>=1.0.1"
@@ -68,10 +81,99 @@ fi
68# ---------------------------------------------------------------------------81# ---------------------------------------------------------------------------
69REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)82REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
70REPO_NAME=$(basename "$REPO_ROOT")83REPO_NAME=$(basename "$REPO_ROOT")
71-OAT_REPORT_DIR="$REPO_ROOT/oat_reports"84+OAT_REPORT_DIR="${TMPDIR:-/tmp}/oat_reports_$$"
85+OAT_RESULT_DIR="$REPO_ROOT/oat_reports"
liu-wei
liu-weiliu-wei7月3日

🟡 Medium | OAT_REPORT_DIR 路径迁移到 /tmp 是行为变更,PR 描述 / changelog 都没声明

事实

-OAT_REPORT_DIR="$REPO_ROOT/oat_reports"
+OAT_REPORT_DIR="${TMPDIR:-/tmp}/oat_reports_$$"
+OAT_RESULT_DIR="$REPO_ROOT/oat_reports"

影响(用户/工具链可能踩的坑):

  1. 完整 OAT 报告 PlainReport_*.txt 不再在仓库内——之前开发者去 <repo>/oat_reports/PlainReport_*.txt 复盘报错,现在整目录在 /tmp/ 跑完就 rm -rf 删掉(脚本末尾的 rm -rf "$OAT_REPORT_DIR"
  2. OAT_RESULT_DIR(= 原 oat_reports/)里只剩 result.txt 摘要——<repo>/oat_reports/PlainReport_*.txt 路径断了
  3. 任何依赖完整报告路径的:本地排查脚本、CI 报表归档、文档里"看 OAT 报错请打开 /oat_reports/..."的指引——全都会 broken

修法二选一

  • (推荐) 把 OAT_RESULT_DIR 也保留 PlainReport_*.txt,不 rm -rf——在 repo 里能查到完整报告,temp 目录只放中间过程
  • 或在 PR 描述里显式声明这个行为变更 + 在 docs / CONTRIBUTING.md 里同步更新"看 OAT 报告的路径"

为什么不阻塞但要发

  • 这是对下游用户可见的行为变化
  • PR 描述里只提了"修复 precommit 的 oat 检查抢资源问题",没提报告路径变了
  • 不算 bug,但属于 changelog 必填项
likedislike
72 86 
73-echo "[OAT] Running OAT scan (Python Edition) — INCREMENTAL MODE"87+# ---------------------------------------------------------------------------
88+# 2a. PR-range deduplication (pure git, no CI env vars required)
89+#
90+# Strategy:
91+# 1. Find the merge-base between HEAD and the upstream branch (origin/master
92+# or similar). If found, this is a feature-branch context ??collect ALL
93+# files changed since the branch diverged (full PR diff).
94+# 2. Key a done-marker on the HEAD SHA. The first invocation runs the scan;
95+# every subsequent invocation for the same HEAD exits 0 immediately.
96+# This eliminates redundant scans when CI calls the hook once per commit.
97+# 3. If no merge-base is found (e.g. committing directly on master) fall back
98+# to scanning only the currently staged files, with no done-marker.
99+# ---------------------------------------------------------------------------
100+_PR_MERGE_BASE=""
101+_DONE_MARKER=""
102+_HEAD_SHA=$(git rev-parse HEAD 2>/dev/null | cut -c1-12 || true)
103+ 
104+if [ -n "$_HEAD_SHA" ]; then
105+ # Try to find merge-base with a known upstream branch
106+ if [ -n "${PRE_COMMIT_FROM_REF:-}" ]; then
107+ # pre-commit --from-ref/--to-ref: use the provided base directly
108+ _PR_MERGE_BASE=$(git merge-base "$PRE_COMMIT_FROM_REF" HEAD 2>/dev/null || true)
109+ fi
110+ if [ -z "$_PR_MERGE_BASE" ]; then
111+ # Search all remotes (origin, upstream, etc.) for common trunk branch names.
112+ # This handles fork setups where origin=fork and upstream=main-repo.
113+ _CANDIDATE_BASE=""
114+ _CANDIDATE_DIST=0
115+ for _b in $(git for-each-ref --format='%(refname:short)' \
116+ 'refs/remotes/*/master' 'refs/remotes/*/main' \
117+ 'refs/remotes/*/develop' 'refs/remotes/*/dev' 2>/dev/null); do
118+ _mb=$(git merge-base HEAD "$_b" 2>/dev/null || true)
119+ [ -z "$_mb" ] && continue
120+ # Skip if merge-base is HEAD itself (branch is ahead of or equal to HEAD)
121+ [ "$_mb" = "$(git rev-parse HEAD 2>/dev/null)" ] && continue
122+ # Pick the candidate whose merge-base is furthest from HEAD
123+ # (most commits since divergence = most likely true PR base)
124+ _dist=$(git rev-list --count "$_mb"..HEAD 2>/dev/null || echo 0)
125+ if [ -z "$_CANDIDATE_BASE" ] || [ "$_dist" -gt "$_CANDIDATE_DIST" ]; then
126+ _CANDIDATE_BASE="$_mb"
127+ _CANDIDATE_DIST="$_dist"
128+ fi
129+ done
130+ _PR_MERGE_BASE="$_CANDIDATE_BASE"
131+ fi
132+ 
133+ # Only use PR-range mode when HEAD has diverged from the upstream base
134+ if [ -n "$_PR_MERGE_BASE" ] && [ "$_PR_MERGE_BASE" != "$(git rev-parse HEAD 2>/dev/null)" ]; then
135+ mkdir -p "$OAT_RESULT_DIR"
136+ _DONE_MARKER="$OAT_RESULT_DIR/.done_${_HEAD_SHA}"
137+ if [ -f "$_DONE_MARKER" ]; then
138+ echo "[OAT] [SKIP] Already scanned HEAD=${_HEAD_SHA}. Skipping duplicate invocation."
139+ exit 0
140+ fi
141+ else
142+ # HEAD == merge-base: on the upstream branch itself, no PR range
143+ _PR_MERGE_BASE=""
144+ fi
145+fi
146+ 
147+# ---------------------------------------------------------------------------
148+# 2b. Deduplicate parallel invocations within the same pre-commit run.
149+# Only the first instance that acquires the lock actually runs the scan;
150+# all others wait then exit 0.
151+# ---------------------------------------------------------------------------
152+if command -v flock >/dev/null 2>&1; then
153+ _OAT_SCAN_LOCK="${TMPDIR:-/tmp}/oat_scan_$(echo "$REPO_ROOT" | tr '/\\: ' '____').lock"
154+ exec 9>"$_OAT_SCAN_LOCK"
155+ if ! flock -n 9; then
156+ echo "[OAT] Another OAT scan instance is running. Waiting..."
157+ flock -w 120 9
158+ # Re-check done marker: if the scanning instance completed normally, skip.
159+ # If it exited abnormally (no marker), fall through and run the scan ourselves.
160+ if [ -n "$_DONE_MARKER" ] && [ -f "$_DONE_MARKER" ]; then
161+ echo "[OAT] Scan already completed by another instance. Skipping."
162+ exit 0
163+ fi
164+ echo "[OAT] Previous instance did not complete. Proceeding with scan..."
165+ # Fall through to run the scan below
166+ fi
167+ # This instance now owns the lock and will run the scan.
168+fi
169+ 
170+echo "[OAT] Running OAT scan (Python Edition) ??INCREMENTAL MODE"
74echo "[OAT] Project: $REPO_NAME"171echo "[OAT] Project: $REPO_NAME"
172+if [ -n "$_PR_MERGE_BASE" ]; then
173+ echo "[OAT] Mode: PR range ??scanning all files changed since merge-base"
174+else
175+ echo "[OAT] Mode: staged files ??scanning only currently staged files"
176+fi
75 177 
76# ---------------------------------------------------------------------------178# ---------------------------------------------------------------------------
77# 3. Collect staged files179# 3. Collect staged files
@@ -93,7 +195,31 @@ if [ $# -gt 0 ]; then
93 FILE_LIST="$FILE_LIST,$_abs"195 FILE_LIST="$FILE_LIST,$_abs"
94 fi196 fi
95 done197 done
198+elif [ -n "$_PR_MERGE_BASE" ]; then
199+ # PR range mode: collect ALL files changed since the branch diverged
200+ _STAGED=$(git diff --name-only --diff-filter=ACM "$_PR_MERGE_BASE" HEAD \
201+ 2>/dev/null || true)
202+ if [ -z "$_STAGED" ]; then
203+ echo "[OAT] No files changed in PR range. Skipping."
204+ [ -n "$_DONE_MARKER" ] && touch "$_DONE_MARKER" 2>/dev/null || true
205+ exit 0
206+ fi
207+ FILE_COUNT=$(echo "$_STAGED" | wc -l | tr -d ' ')
208+ FILE_LIST=""
209+ for _f in $_STAGED; do
210+ case "$_f" in
211+ /*) _abs="$_f" ;;
212+ *) _abs="$REPO_ROOT/$_f" ;;
213+ esac
214+ [ -f "$_abs" ] || continue
215+ if [ -z "$FILE_LIST" ]; then
216+ FILE_LIST="$_abs"
217+ else
218+ FILE_LIST="$FILE_LIST,$_abs"
219+ fi
220+ done
96else221else
222+ # Staged files mode: on upstream branch directly, scan only staged files
97 _STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)223 _STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)
98 if [ -z "$_STAGED" ]; then224 if [ -z "$_STAGED" ]; then
99 echo "[OAT] No staged files to check. Skipping."225 echo "[OAT] No staged files to check. Skipping."
@@ -123,19 +249,13 @@ fi
123echo "[OAT] Checking $FILE_COUNT staged file(s)..."249echo "[OAT] Checking $FILE_COUNT staged file(s)..."
124 250 
125# ---------------------------------------------------------------------------251# ---------------------------------------------------------------------------
126-# 4. Ensure oat_reports/ exists252+# 4. Ensure report directories exist
127# ---------------------------------------------------------------------------253# ---------------------------------------------------------------------------
128mkdir -p "$OAT_REPORT_DIR"254mkdir -p "$OAT_REPORT_DIR"
129- 255+mkdir -p "$OAT_RESULT_DIR"
130-_GITIGNORE="$REPO_ROOT/.gitignore"
131-for _entry in "oat_reports" "log"; do
132- if ! grep -qE "^${_entry}/?$" "$_GITIGNORE" 2>/dev/null; then
133- echo "[OAT] [WARNING] '${_entry}/' is not in .gitignore. Consider adding it to avoid committing scan artifacts."
134- fi
135-done
136 256 
137# ---------------------------------------------------------------------------257# ---------------------------------------------------------------------------
138-# 5. Build oat command use OAT.xml if present in repo root258+# 5. Build oat command ??use OAT.xml if present in repo root
139# ---------------------------------------------------------------------------259# ---------------------------------------------------------------------------
140_OAT_CMD="$_PYTHON -m oat -mode s -s $REPO_ROOT -r $OAT_REPORT_DIR -n $REPO_NAME -w 1 -f $FILE_LIST"260_OAT_CMD="$_PYTHON -m oat -mode s -s $REPO_ROOT -r $OAT_REPORT_DIR -n $REPO_NAME -w 1 -f $FILE_LIST"
141 261 
@@ -169,7 +289,7 @@ fi
169# Only: Invalid File Type + License Header Invalid (no copyright)289# Only: Invalid File Type + License Header Invalid (no copyright)
170# ---------------------------------------------------------------------------290# ---------------------------------------------------------------------------
171REPORT_FILE="$OAT_REPORT_DIR/PlainReport_${REPO_NAME}.txt"291REPORT_FILE="$OAT_REPORT_DIR/PlainReport_${REPO_NAME}.txt"
172-RESULT_FILE="$OAT_REPORT_DIR/result.txt"292+RESULT_FILE="$OAT_RESULT_DIR/result.txt"
173 293 
174# Section headers used as stop-boundaries when extracting sections294# Section headers used as stop-boundaries when extracting sections
175_ALL_HEADERS="Invalid File Type Total Count:|License Not Compatible Total Count:|License Header Invalid Total Count:|Copyright Header Invalid Total Count:|No License File Total Count:|No Readme.OpenSource Total Count:|No Readme Total Count:|Import Invalid Total Count:|Redundant License File Total Count:|Third Party Software Info Total Count:"295_ALL_HEADERS="Invalid File Type Total Count:|License Not Compatible Total Count:|License Header Invalid Total Count:|Copyright Header Invalid Total Count:|No License File Total Count:|No Readme.OpenSource Total Count:|No Readme Total Count:|Import Invalid Total Count:|Redundant License File Total Count:|Third Party Software Info Total Count:"
@@ -200,17 +320,29 @@ _extract_section() {
200 320 
201if [ ! -f "$REPORT_FILE" ]; then321if [ ! -f "$REPORT_FILE" ]; then
202 if [ "$_OAT_RC" -eq 0 ]; then322 if [ "$_OAT_RC" -eq 0 ]; then
323+ # oat exited cleanly with no report: all staged files were filtered out
203 echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)."324 echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)."
325+ [ -n "$_DONE_MARKER" ] && touch "$_DONE_MARKER" 2>/dev/null || true
326+ rm -rf "$OAT_REPORT_DIR"
204 exit 0327 exit 0
205 else328 else
206- echo "[OAT] [WARNING] Report not found: $REPORT_FILE"329+ # oat returned exit code 1 but produced no report — possible disk issue
330+ # or oat internal error. Do not silently pass; block the commit.
331+ echo ""
332+ echo "[OAT] [ERROR] oat exited with code $_OAT_RC but no report was generated."
333+ echo "[OAT] This may indicate a disk error or an oat internal bug."
334+ echo "[OAT] To investigate, run manually:"
335+ echo " $_OAT_CMD"
336+ echo "[OAT] Blocking commit to prevent silent compliance bypass."
337+ echo ""
338+ rm -rf "$OAT_REPORT_DIR"
207 exit 1339 exit 1
208 fi340 fi
209fi341fi
210 342 
211-# Parse counts343+# Parse counts ??use || true to prevent set -e from triggering if grep finds no match
212-_INVALID_TYPE=$(grep "^Invalid File Type Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1)344+_INVALID_TYPE=$(grep "^Invalid File Type Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1 || true)
213-_LICENSE_INVALID=$(grep "^License Header Invalid Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1)345+_LICENSE_INVALID=$(grep "^License Header Invalid Total Count:" "$REPORT_FILE" | grep -oE '[0-9]+' | head -1 || true)
214_INVALID_TYPE=${_INVALID_TYPE:-0}346_INVALID_TYPE=${_INVALID_TYPE:-0}
215_LICENSE_INVALID=${_LICENSE_INVALID:-0}347_LICENSE_INVALID=${_LICENSE_INVALID:-0}
216 348 
@@ -239,11 +371,8 @@ _SECTION_LIC=$(_extract_section "$REPORT_FILE" "License Header Invalid Total Cou
239 echo "==================================="371 echo "==================================="
240} > "$RESULT_FILE"372} > "$RESULT_FILE"
241 373 
242-# Clean up full plain report (keep only result.txt)
243-rm -f "$REPORT_FILE"
244- 
245# ---------------------------------------------------------------------------374# ---------------------------------------------------------------------------
246-# 8. Block commit if issues found375+# 8. Block commit if issues found; always clean up temp dir
247# ---------------------------------------------------------------------------376# ---------------------------------------------------------------------------
248TOTAL_ISSUES=$(( _INVALID_TYPE + _LICENSE_INVALID ))377TOTAL_ISSUES=$(( _INVALID_TYPE + _LICENSE_INVALID ))
249 378 
@@ -257,12 +386,15 @@ if [ "$TOTAL_ISSUES" -gt 0 ]; then
257 echo " - Invalid File Type: $_INVALID_TYPE"386 echo " - Invalid File Type: $_INVALID_TYPE"
258 echo " - License Header Invalid: $_LICENSE_INVALID"387 echo " - License Header Invalid: $_LICENSE_INVALID"
259 echo ""388 echo ""
260- echo "[OAT] Details:"389+ echo "[OAT] Details (also saved to: $RESULT_FILE):"
261- echo " cat $RESULT_FILE"390+ echo "---"
391+ cat "$RESULT_FILE"
392+ echo "---"
262 echo ""393 echo ""
263 echo "Fix the issues and recommit, or skip with:"394 echo "Fix the issues and recommit, or skip with:"
264 echo " git commit --no-verify"395 echo " git commit --no-verify"
265 echo ""396 echo ""
397+ rm -rf "$OAT_REPORT_DIR"
266 exit 1398 exit 1
267fi399fi
268 400 
@@ -270,4 +402,10 @@ echo ""
270echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)."402echo "[OAT] [OK] All checks passed ($FILE_COUNT file(s) checked)."
271echo "[OAT] Summary: cat $RESULT_FILE"403echo "[OAT] Summary: cat $RESULT_FILE"
272echo ""404echo ""
405+# Mark scan as done for CI range mode (prevents redundant re-runs on same PR head)
406+if [ -n "$_DONE_MARKER" ]; then
407+ touch "$_DONE_MARKER" 2>/dev/null || true
408+ echo "[OAT] Done-marker created: $_DONE_MARKER"
409+fi
410+rm -rf "$OAT_REPORT_DIR"
273exit 0411exit 0