Skip to content

fix(feishu): inbound files dropped on captioned or multi-file posts - #151896

Merged
obviyus merged 10 commits into
openclaw:mainfrom
gokay-ai:cursor/feishu-post-files-9c93
Oct 1, 2026
Merged

obviyus merged 10 commits into
openclaw:mainfrom
gokay-ai:cursor/feishu-post-files-9c93

Conversation

@gokay-ai

@gokay-ai gokay-ai commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #151792

What Problem This Solves

Feishu sends files with captions and multi-file messages as rich-text post payloads with a top-level files[] array. The parser previously retained only the caption and inline media, silently dropping those files.

User Impact

Captioned and files-only posts now deliver their files through the existing resource download and attachment-storage path. Top-level files retain standalone-document semantics; existing inline media remains video even when its filename has no recognized extension. Persisted replay identities remain unchanged, so redelivery after an upgrade does not run an old message again.

Why This Change Was Made

The existing post parser collects validated top-level file keys, ignores folders and invalid keys, and avoids duplicate downloads. The replay-key reader explicitly retains the shipped inline-only projection. No configuration, public SDK, database schema, retention, or permission changes are required.

The follow-up removes stray closing tokens from the existing handler test, preserves inline-video classification, and consolidates the two post-file handler cases into the existing handler fixture instead of keeping a separate duplicated runtime mock and test-routing entry.

Original implementation: @gokay-ai. Report and document-classification diagnosis: @WhitenessTiger. Related alternative: #153934.

Maintainer intent: Sarah's #158307 only adds an unrelated Agents API persistence-test entry to the shared test-routing file; that entry is unchanged. No overlap with Pash changes or Sarah's production behavior.

Evidence

An isolated, built Gateway received signed Feishu webhooks with the bundled Feishu plugin pointed at a local HTTPS API. The Lark token exchange, resource download, attachment storage, and agent-dispatch context were real. A proof plugin captured the public reply_dispatch context instead of calling a model; no real Feishu account was used.

Baseline: current main 00f3a2063022388cb7907964fe098537fbe3fd15.

Scenario Baseline Candidate
Post with caption and files[] Caption, zero attachments Same caption, one downloaded document
Post without files[] Caption, zero attachments Same caption, zero attachments

Both webhook responses were HTTP 200. The candidate attachment was read from the path in the actual agent context and compared byte-for-byte with the mock API response. Its SHA-256 was 39cc4c3d88a39a68781faf6c3c3d7f877fbf6d51875cc2d8a82fbc775beab0a7; MIME was text/csv, kind was document. Gateway proof took 13.59s on baseline and 13.10s on candidate, including startup and shutdown.

The baseline media regression had three failures (captioned, multi-file, and unnamed top-level files all yielded no downloads) and two passing controls. The candidate parser/media/replay group passed all 36 tests. Existing handler and durable-ingress cases passed 115 tests, including the seeded pre-upgrade replay record. The consolidated captioned/files-only handler cases passed separately.

Limitations: this is isolated Gateway/channel-boundary proof using a local API and a substituted model backend, not a live Feishu service or model-provider test. Full repository checks were not run locally; hosted CI owns those lanes.

Final focused runs all passed with --maxWorkers=1 (wrapper wall times):

File Wall time
post.test.ts 4.19s
bot-content.media.test.ts 2.12s
dedupe-key.test.ts 1.96s
bot.test.ts 75.14s
feishu-ingress.test.ts 19.78s

The two consolidated handler cases also failed against the current-main parser: the captioned case had no media paths and the files-only case never reached agent context. Restoring the candidate parser passed both. The existing handler fixture now covers these cases without the former 468-line duplicated runtime mock. git diff --check passed.

CI follow-up

Run 36811635616 passed the changed extension bundle (267s), compact-large shard (60s), and changed-boundary shard (129s), with zero cancelled jobs. Its test-type check caught an optional saved.id in the newly consolidated mock. The follow-up uses the required resource key as that ID. The full extension-test typecheck and both affected handler cases then passed in secretless Docker. This is a test-only correction; the Gateway byte proof's production inputs are unchanged.

Legacy replay follow-up

The exact-head review found one additional replay consumer: the monitor's retained raw-message-ID bridge. It now uses the same inline-only parser projection as the replay-key owner. The existing real-monitor replay fixture was extended with a timestamped captioned files[] redelivery after a seeded raw-ID record and database reopen. It failed before the correction (two agent dispatches instead of one), then passed afterward; fresh-post and inline-image revision controls remain covered. Wrapper wall was 96.58s, including a cold 65.66s worker compilation; the test itself took 1.987s.

The corrected production runtime was rebuilt and the signed-webhook Gateway byte proof was rerun successfully: the captioned post delivered the identical CSV document, and the attachment-free control still delivered only its caption. No Pash/Sarah history overlaps the newly touched monitor files.

Copilot AI lite review requested due to automatic review settings September 18, 2026 15:13
@clawsweeper

clawsweeper Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: feishu Channel integration: feishu size: M labels Sep 18, 2026
@clawsweeper clawsweeper Bot added P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Sep 18, 2026
@clawsweeper

clawsweeper Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed October 1, 2026, 12:25 AM ET / 04:25 UTC (Revision 13).

ClawSweeper review

What this changes

The Feishu plugin downloads files from captioned and multi-file rich-text posts while preserving inline-media classification and existing replay identities.

Merge readiness

✅ Ready for maintainer review

The fix remains necessary on current main and the latest release. Prior blockers are resolved, the recorded Gateway proof covers the repaired attachment path, and no new actionable defect was found.

Priority: P1
Reviewed head: 22d71d93eebedc7f8dc24932c3aa5258b3b0ae47

Review scores

Measure Result What it means
Overall readiness 🦞 diamond lobster (5/6) Focused ownership, meaningful before/after Gateway evidence, and repaired replay compatibility provide above-average review confidence.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): Recorded signed-webhook Gateway observations exercise the changed parser, real Lark resource client against local HTTPS, attachment storage, and agent context: the candidate delivers verified CSV bytes where baseline delivered none. The unchanged control and rerun after replay correction support reuse on the formatting-only head. Persisted identities remain unchanged, with reopened-state regression coverage; no stored-data model changes.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): Recorded signed-webhook Gateway observations exercise the changed parser, real Lark resource client against local HTTPS, attachment storage, and agent context: the candidate delivers verified CSV bytes where baseline delivered none. The unchanged control and rerun after replay correction support reuse on the formatting-only head. Persisted identities remain unchanged, with reopened-state regression coverage; no stored-data model changes.
Evidence reviewed 8 items Pinned introduced changes: Reviewed the merge-base-to-head changes, excluding base-only run-node changes. The parser adds validated top-level files to the existing attachment path; no dependency, workflow, public SDK, configuration, or permission changes are introduced.
Current-main necessity: Current main's post parser collects inline image/media elements but neither retains nor collects top-level files. The reported captioned and files-only payloads therefore still lose their attachments.
Latest-release check: The supplied latest release, v2026.9.7, also lacks top-level post-file collection. This PR's requested repair is not established as shipped.
Findings None None.
Security None None.

How this fits together

The Feishu plugin turns incoming channel messages into text and saved attachments for the agent. Its replay guard prevents previously processed messages from starting another turn.

flowchart TD
  A[Incoming Feishu post] --> B[Replay guard]
  B -->|Previously processed| C[Suppress duplicate]
  B -->|New message| D[Parse caption and attachments]
  D --> E[Download message resources]
  E --> F[Save attachments]
  F --> G[Agent message context]
Loading

Before merge

None.

Agent review details

Security

None.

PR surface

Source +60, Tests +479, Docs +4. Total +543 across 11 files.

View PR surface stats
Area Files Added Removed Net
Source 4 67 7 +60
Tests 6 485 6 +479
Docs 1 4 0 +4
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 11 556 13 +543

Review metrics

Metric Value Why it matters
Production versus regression coverage production +60 LOC net; tests +479 LOC net Production growth is justified by validated file collection and origin classification; regressions cover delivery and retained replay state.

Technical review

Best possible solution:

Keep attachment collection in the existing Feishu parser and download owner while retaining the shipped replay projection and inline-video semantics.

Do we have a high-confidence way to reproduce the issue?

Yes: the reported post payloads contain top-level files that current main never collects, and the recorded Gateway baseline confirms zero attachments. This read-only review did not independently execute the reproduction.

Is this the best way to solve the issue?

Yes: extending the existing parser and resource path repairs the missing payload shape without a competing owner, while both replay consumers preserve existing identities.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against d7b029347fd0.

Labels

Label changes:

  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.

Label justifications:

  • P1: Captioned and multi-file Feishu messages silently lose attachments on current main and the latest release.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): Recorded signed-webhook Gateway observations exercise the changed parser, real Lark resource client against local HTTPS, attachment storage, and agent context: the candidate delivers verified CSV bytes where baseline delivered none. The unchanged control and rerun after replay correction support reuse on the formatting-only head. Persisted identities remain unchanged, with reopened-state regression coverage; no stored-data model changes.
  • proof: sufficient: Contributor real behavior proof is sufficient. Recorded signed-webhook Gateway observations exercise the changed parser, real Lark resource client against local HTTPS, attachment storage, and agent context: the candidate delivers verified CSV bytes where baseline delivered none. The unchanged control and rerun after replay correction support reuse on the formatting-only head. Persisted identities remain unchanged, with reopened-state regression coverage; no stored-data model changes.

Evidence

What I checked:

  • Pinned introduced changes: Reviewed the merge-base-to-head changes, excluding base-only run-node changes. The parser adds validated top-level files to the existing attachment path; no dependency, workflow, public SDK, configuration, or permission changes are introduced. (extensions/feishu/src/post.ts:177, 22d71d93eebe)
  • Current-main necessity: Current main's post parser collects inline image/media elements but neither retains nor collects top-level files. The reported captioned and files-only payloads therefore still lose their attachments. (extensions/feishu/src/post.ts, d7b029347fd0)
  • Latest-release check: The supplied latest release, v2026.9.7, also lacks top-level post-file collection. This PR's requested repair is not established as shipped. (extensions/feishu/src/post.ts, c074824a27c9)
  • Replay compatibility and prior findings: Both the replay-key reader and monitor's retained raw-ID bridge disable top-level-file collection. The monitor regression seeds a legacy record, reopens SQLite, suppresses captioned files[] redelivery, and retains fresh-post and inline-image revision controls. Top-level documents and unnamed/named inline video are separately covered; the previous test syntax and optional-ID defects are absent. (extensions/feishu/src/monitor.message-handler.ts:430, 22d71d93eebe)
  • Recorded production-boundary proof: The captured PR body reports signed webhooks entering an isolated built Gateway, real Lark token/resource requests against local HTTPS, attachment storage, and captured public agent context. Baseline produced zero attachments; candidate delivered one text/csv document with verified byte equality and SHA-256 39cc4c3d88a39a68781faf6c3c3d7f877fbf6d51875cc2d8a82fbc775beab0a7. The attachment-free control stayed unchanged, and proof was rerun after the monitor correction. This is channel-boundary evidence, not a live Feishu tenant or model-provider run; the reviewer did not execute contributor code.
  • Re-review continuity: GitHub's exact-head commit patch confirms that the only change since the previous completed review is formatting the legacy replay guard. Local comparison encountered an unavailable promisor blob with HTTP 403; the read-only GitHub commit response supplied the exact parent and patch instead. The recorded original head has parent 75e4479; the stale test merge was not used to infer removals. (extensions/feishu/src/monitor.message-handler.ts:430, 22d71d93eebe)

Likely related people:

  • unknown: The claimed source-line change could not be verified from bounded local history. (role: source history unknown; confidence: low)
  • unknown: The claimed source-line change could not be verified from bounded local history. (role: source history unknown; confidence: low)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (12 earlier review cycles; latest 8 shown)
  • reviewed 2026-09-21T16:07:03.068Z sha 5f9de8b :: needs real behavior proof before merge. :: [P2] [P2] Keep unnamed top-level files classified as documents
  • reviewed 2026-09-22T10:49:10.106Z sha 84a6123f17f010b0dff7e400a4c68ef0e1af649e :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-23T10:57:01.040Z sha 289bc817f5ca7ef47a058f370a24e402b7224bd3 :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-23T15:27:04.405Z sha 81517359f35e99734923daddb4c8e1f2713202db :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-28T10:33:47.119Z sha bc1cb02 :: needs real behavior proof before merge. :: [P2] Remove the unmatched closing tokens from the Feishu test | [P2] Keep unknown named inline media on the video path
  • reviewed 2026-10-01T03:49:35.222Z sha 6abae1b :: needs maintainer review before merge. :: none
  • reviewed 2026-10-01T04:00:06.937Z sha 2380f9e :: blocked before merge. :: [P1] [P1] Preserve the monitor’s legacy replay projection
  • reviewed 2026-10-01T04:13:36.108Z sha 75e4479 :: needs maintainer review before merge. :: none

@gokay-ai

Copy link
Copy Markdown
Contributor Author

Replay-key P1 is addressed on this head: captioned-post files[] still download into agent context, but persisted replay identity stays on the shipped post key (inline media only). A seeded pre-upgrade om_post record now suppresses redelivery under a fresh event ID. Fixture/integration coverage was added; live Feishu verification was not run.

@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. label Sep 21, 2026
@cursor
cursor Bot force-pushed the cursor/feishu-post-files-9c93 branch from 1026276 to 7339990 Compare September 21, 2026 06:49
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. label Sep 21, 2026
@gokay-ai

Copy link
Copy Markdown
Contributor Author

Unnamed top-level files[] were landing as video even when Feishu later handed back a PDF name/type. Parser now keeps those as documents; unnamed inline media still falls back to video.

@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. label Sep 22, 2026
Preserve shipped post replay keys so a pre-upgrade captioned post
persisted as om_post still suppresses redelivery after top-level
files[] are downloaded.

Signed-off-by: GokayAI <[email protected]>
Move captioned/multi-file handleFeishuMessage coverage into a sibling
file so bot.test.ts does not grow past the line-cap ratchet. Type the
media saver mock so mock.calls mapping typechecks.

Signed-off-by: GokayAI <[email protected]>
Top-level files[] without file_name were classified as video, so a
download-supplied PDF name and MIME still skipped file extraction.
Carry document origin from the parser and keep the inline-media fallback.

Signed-off-by: GokayAI <[email protected]>
Use the live mocked saver export and reset Feishu dedupe so captioned
and multi-file handleFeishuMessage coverage still records downloads in
the shared extension worker. Give mockCallArg a type-predicate slot so
oxlint no-unnecessary-type-parameters passes.

Signed-off-by: GokayAI <[email protected]>
Route bot.post-files.test.ts with bot.test.ts onto isolated database-worker
forks so shared extension-feishu mocks cannot steal media.js. Assert and
stub the live saveMessageResourceFeishu export resolveFeishuMediaList calls.

Signed-off-by: GokayAI <[email protected]>
@gokay-ai
gokay-ai force-pushed the cursor/feishu-post-files-9c93 branch from 8151735 to bc1cb02 Compare September 28, 2026 10:24
@obviyus

obviyus commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Oct 1, 2026
@obviyus

obviyus commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Oct 1, 2026
@obviyus

obviyus commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Oct 1, 2026
@obviyus
obviyus merged commit 7b7d195 into openclaw:main Oct 1, 2026
95 checks passed
@obviyus

obviyus commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Merged — thank you @gokay-ai for the original fix, and @WhitenessTiger for the report and document-classification diagnosis. Captioned Feishu post files now reach agent context through the existing download path, with byte-level Gateway proof and retained replay compatibility.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Oct 2, 2026
…penclaw#151896)

Feishu `post` messages that carried top-level `files[]` attachments only passed the caption to the agent: the files were never downloaded. They're now downloaded and attached to the agent context, and the legacy replay of stored records projects them the same way.

Fixes openclaw#151792. Thanks @gokay-ai for the fix, and @Leon-SK668 for the alternative in openclaw#153934 and its byte-level proof.

Proof: a real isolated Gateway with a signed Feishu webhook and a local HTTPS API.
- On main, the agent received only the caption. With this change, it receives the caption plus a byte-identical CSV document.
- An attachment-free post is unchanged.
- A seeded legacy-record replay through the actual monitor fails before the fix and passes after.

Co-authored-by: Ayaan Zaidi <[email protected]>
yozakura-ava pushed a commit to yozakura-ava/openclaw that referenced this pull request Oct 6, 2026
…penclaw#151896)

Feishu `post` messages that carried top-level `files[]` attachments only passed the caption to the agent: the files were never downloaded. They're now downloaded and attached to the agent context, and the legacy replay of stored records projects them the same way.

Fixes openclaw#151792. Thanks @gokay-ai for the fix, and @Leon-SK668 for the alternative in openclaw#153934 and its byte-level proof.

Proof: a real isolated Gateway with a signed Feishu webhook and a local HTTPS API.
- On main, the agent received only the caption. With this change, it receives the caption plus a byte-identical CSV document.
- An attachment-free post is unchanged.
- A seeded legacy-record replay through the actual monitor fails before the fix and passes after.

Co-authored-by: Ayaan Zaidi <[email protected]>
(cherry picked from commit 7b7d195)
obviyus added a commit that referenced this pull request Oct 18, 2026
…e commit (#176065)

Related: #176053 (merged; it trims root `AGENTS.md` under the cap)

## What Problem This Solves

Fixes: PRs fail CI on their first push for generated files, docs lint and size caps that a seconds-fast local check would have caught, and root `AGENTS.md` regrows past the 20,000-character cap that OpenClaw loads whole.

## User Impact

No product change. Contributors and agents see these failures at commit time with the regen command, not after a CI round trip. A tracked `AGENTS.md` over the workspace bootstrap cap now fails CI.

## Why This Change Was Made

- **Pre-commit freshness checks.** `git-hooks/pre-commit` (installed by `pnpm install`) now runs existing `--check` scripts, chosen from staged paths and run in parallel: `lint:docs` on changed docs, `protocol:check`, `native:i18n:verify`, `config:docs:check`, the line-cap ratchet, and the new AGENTS.md size check. Each failure prints its fix command. Typecheck, lint and tests stay with hosted CI, and commits replayed by merge, rebase, cherry-pick or revert skip the checks. The content guard (`guard-staged-content.mjs`) runs the step and redacts its output with its existing redactor, so blocked private literals are never echoed. The hook judges only the bytes being committed: checks that import the working-tree code graph (protocol, native i18n, config docs, AGENTS.md size) are skipped with a one-line note when any tracked file has unstaged edits, `lint:docs` is skipped only when its own files or config are dirty, and the line-cap ratchet reads the index and always runs. Plain `node` package scripts start without pnpm. Path predicates live in their owners: `changed-lanes.mts` (also used by `check:changed` for the line-cap ratchet) and `hasProtocolGenerationInput` shared with CI's check family scope.
- **AGENTS.md size check.** `scripts/check-agents-md-size.mts` reads the cap from `resolveBootstrapMaxChars()` instead of copying the number. It runs in the hook, `pnpm check` and the CI check-docs job.

Left out on purpose, because each is too slow or already red on `main` for unrelated files: max-lines ratchet (~30 s), knip unused files (92 s), madge cycles (22 s), Control UI i18n (16 s), plugin SDK API diff (two full builds).

## Evidence

Back-test against first-push CI failures from a 90-day sample of merged PRs (26 rows; 2 retired checks; detached worktrees at each failing head):

| Row | CI failure | Hook before | Hook after |
|---|---|---|---|
| #122960 | docs MD051 link fragments | pass | **fail**, same 3 errors as CI |
| #112339 | Swift protocol models drift | pass | **fail**, same drift |
| #115577 | native i18n inventory drift | pass | **fail**, same message |
| #165543 | config baseline hash | pass | **fail**, same hash mismatch |
| #151896 | line-cap growth 3557 → 3676 | pass | **fail**, same counts |

The other 19 live rows need typecheck, tests, UI build or slow audits, which stay in hosted CI by design.

Size check back-test: it first fails at bb7d32e (#167739, 20,414 chars), and fails again at b19d554 (#173903) after the earlier trim.

Freshness step wall time, median of 3 on a warm checkout (before = previous head 7aae979, after = this head):

| Staged change | Checks | Before | After |
|---|---|---|---|
| nothing | none | 0.17 s | 0.28 s |
| `CHANGELOG.md` | none | 0.20 s | 0.24 s |
| one docs page | lint:docs | 0.92 s | 1.13 s |
| one `src/**/*.ts` | line-cap | 1.29 s | 1.42 s |
| a scoped `AGENTS.md` | size check | 0.92 s | 1.07 s |
| protocol schema `.ts` | protocol, line-cap | 4.77 s | 4.39 s |
| Android Kotlin source | protocol, native i18n | 4.41 s | 4.48 s |
| plugin manifest | config docs | 8.19 s | 8.97 s |

Spawning `node` directly saves about 0.1–0.3 s per check against `pnpm -s`; the after column also pays one extra `git diff` for the unstaged-input rule. Most of the remaining time is in the checks themselves. `protocol:check` and `config:docs:check` are not cached: their inputs are transitive imports across `src/`, `packages/` and every plugin manifest, and no existing owner lists them reliably.

Checks run: new and existing hook tests, including `test/git-hooks-pre-commit-freshness.test.ts` (real guard, real freshness step and a real `node` runner: a partly staged docs page with a blocked literal in its unstaged edit is skipped; a staged protocol schema with an unstaged edit in an imported owner outside the trigger list skips `protocol:check` while the line-cap ratchet still runs; and a failing check that prints a private literal is redacted), `test/scripts/check-staged-freshness.test.ts`, `changed-lanes.test.ts`, `check.test.ts`, `tsgo:scripts`, `tsgo:test:root`, oxlint, oxfmt. All commits went through the new hook.

**Merge danger:** two-way door; a revert restores the previous hook and CI step. No state, config or published contract changes. Blast radius: every local commit in this repository (adds about 0.3 s to 9 s depending on staged paths) and the CI check-docs job. #176053 has landed, so the size check passes on `main`.

Co-authored-by: Ayaan Zaidi <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Oct 19, 2026
…e commit (openclaw#176065)

Related: openclaw#176053 (merged; it trims root `AGENTS.md` under the cap)

## What Problem This Solves

Fixes: PRs fail CI on their first push for generated files, docs lint and size caps that a seconds-fast local check would have caught, and root `AGENTS.md` regrows past the 20,000-character cap that OpenClaw loads whole.

## User Impact

No product change. Contributors and agents see these failures at commit time with the regen command, not after a CI round trip. A tracked `AGENTS.md` over the workspace bootstrap cap now fails CI.

## Why This Change Was Made

- **Pre-commit freshness checks.** `git-hooks/pre-commit` (installed by `pnpm install`) now runs existing `--check` scripts, chosen from staged paths and run in parallel: `lint:docs` on changed docs, `protocol:check`, `native:i18n:verify`, `config:docs:check`, the line-cap ratchet, and the new AGENTS.md size check. Each failure prints its fix command. Typecheck, lint and tests stay with hosted CI, and commits replayed by merge, rebase, cherry-pick or revert skip the checks. The content guard (`guard-staged-content.mjs`) runs the step and redacts its output with its existing redactor, so blocked private literals are never echoed. The hook judges only the bytes being committed: checks that import the working-tree code graph (protocol, native i18n, config docs, AGENTS.md size) are skipped with a one-line note when any tracked file has unstaged edits, `lint:docs` is skipped only when its own files or config are dirty, and the line-cap ratchet reads the index and always runs. Plain `node` package scripts start without pnpm. Path predicates live in their owners: `changed-lanes.mts` (also used by `check:changed` for the line-cap ratchet) and `hasProtocolGenerationInput` shared with CI's check family scope.
- **AGENTS.md size check.** `scripts/check-agents-md-size.mts` reads the cap from `resolveBootstrapMaxChars()` instead of copying the number. It runs in the hook, `pnpm check` and the CI check-docs job.

Left out on purpose, because each is too slow or already red on `main` for unrelated files: max-lines ratchet (~30 s), knip unused files (92 s), madge cycles (22 s), Control UI i18n (16 s), plugin SDK API diff (two full builds).

## Evidence

Back-test against first-push CI failures from a 90-day sample of merged PRs (26 rows; 2 retired checks; detached worktrees at each failing head):

| Row | CI failure | Hook before | Hook after |
|---|---|---|---|
| openclaw#122960 | docs MD051 link fragments | pass | **fail**, same 3 errors as CI |
| openclaw#112339 | Swift protocol models drift | pass | **fail**, same drift |
| openclaw#115577 | native i18n inventory drift | pass | **fail**, same message |
| openclaw#165543 | config baseline hash | pass | **fail**, same hash mismatch |
| openclaw#151896 | line-cap growth 3557 → 3676 | pass | **fail**, same counts |

The other 19 live rows need typecheck, tests, UI build or slow audits, which stay in hosted CI by design.

Size check back-test: it first fails at 187e04d (openclaw#167739, 20,414 chars), and fails again at 057536d (openclaw#173903) after the earlier trim.

Freshness step wall time, median of 3 on a warm checkout (before = previous head 7aae979, after = this head):

| Staged change | Checks | Before | After |
|---|---|---|---|
| nothing | none | 0.17 s | 0.28 s |
| `CHANGELOG.md` | none | 0.20 s | 0.24 s |
| one docs page | lint:docs | 0.92 s | 1.13 s |
| one `src/**/*.ts` | line-cap | 1.29 s | 1.42 s |
| a scoped `AGENTS.md` | size check | 0.92 s | 1.07 s |
| protocol schema `.ts` | protocol, line-cap | 4.77 s | 4.39 s |
| Android Kotlin source | protocol, native i18n | 4.41 s | 4.48 s |
| plugin manifest | config docs | 8.19 s | 8.97 s |

Spawning `node` directly saves about 0.1–0.3 s per check against `pnpm -s`; the after column also pays one extra `git diff` for the unstaged-input rule. Most of the remaining time is in the checks themselves. `protocol:check` and `config:docs:check` are not cached: their inputs are transitive imports across `src/`, `packages/` and every plugin manifest, and no existing owner lists them reliably.

Checks run: new and existing hook tests, including `test/git-hooks-pre-commit-freshness.test.ts` (real guard, real freshness step and a real `node` runner: a partly staged docs page with a blocked literal in its unstaged edit is skipped; a staged protocol schema with an unstaged edit in an imported owner outside the trigger list skips `protocol:check` while the line-cap ratchet still runs; and a failing check that prints a private literal is redacted), `test/scripts/check-staged-freshness.test.ts`, `changed-lanes.test.ts`, `check.test.ts`, `tsgo:scripts`, `tsgo:test:root`, oxlint, oxfmt. All commits went through the new hook.

**Merge danger:** two-way door; a revert restores the previous hook and CI step. No state, config or published contract changes. Blast radius: every local commit in this repository (adds about 0.3 s to 9 s depending on staged paths) and the CI check-docs job. openclaw#176053 has landed, so the size check passes on `main`.

Co-authored-by: Ayaan Zaidi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu docs Improvements or additions to documentation P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: L status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feishu: inbound files silently dropped when sent as rich-text post (multi-file or file + caption)

3 participants