Skip to content

fix(apps): make cache-clear ask first, and make apps failures classifiable - #2415

Merged
chenxingyang1019 merged 3 commits into
mainfrom
fix/apps-error-contract-cache-confirm
Aug 20, 2026
Merged

fix(apps): make cache-clear ask first, and make apps failures classifiable#2415
chenxingyang1019 merged 3 commits into
mainfrom
fix/apps-error-contract-cache-confirm

Conversation

@chenxingyang1019

@chenxingyang1019 chenxingyang1019 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

本 PR 做两件事,都指向同一个目标:让调用方(尤其是 agent)拿到的失败信封能据以决策。取代 #2361(同内容 + 新增 file/app 级错误码分类)。


一、+cache-clear 必须先确认再清(skill 文档)

现象:用户说「帮我清一下应用 app_xxx 的 online 环境缓存」,agent 从 --help 读到 Risk: high-risk-write 后,自己在首次调用就补上 --yes,真的清掉了 online 缓存,全程没碰到确认关卡。

根因不在 CLI 门禁——门禁本身是好的:不带 --yes → exit 10 confirmation_required--dry-run → exit 0 且不触发门禁。问题在文档措辞只约束了「撞到 exit 10 之后不要静默补 --yes」,没禁止首次调用就预先带上;而「已明确授权可直接带 --yes」又没定义什么叫「授权」,于是「帮我清一下缓存」被读成了授权。

改动:

  • +cache-clear 增加 > [!CAUTION]:默认「先确认、后执行」,未确认时只能 --dry-run 预览或口头请求确认后停下等回话;并写明 exit 10 不是「补 --yes 重试」的信号。

  • 增加零歧义判据表,把「请求清理」和「确认清理」分开。这样在拦住误清的同时,不会把本来正确的行为一起判死:

    用户原话 是否算确认 期望行为
    「帮我清一下 online 环境缓存」 ❌ 请求,不是确认 --dry-run 或停下请求确认
    「我确认清 dev,不要动 online」 ✅ 含确认表述 + 明确环境 直接带 --environment dev --yes
    「清一下缓存」(未说环境) ❌ 且环境未定 请用户确认环境,严禁自选
    「先给我看 dry-run,不要真执行」 ❌ 未确认 只出预览
  • +cache-delete 小节点名响应字段 deleted_key_count0 表示 key 原本就不存在,不得表述成「已成功删除」;并给出「删前 get → delete 得 1 → 删后 get miss」的证明链(只靠删后一次 miss 无法区分「删掉了」和「本来就没有」)。

  • SKILL.md 的「禁止预授权判定底线」新增 +cache-clear——该清单即便用户已预授权也不豁免,这是二级文档管不到的场景。路由表加风险标注:同表其它高危命令(+file-delete+role-delete+member-remove)都没有,保持一致。


二、让 apps 域的失败可分类

2.1 hint 不再套到 precondition 类失败上

+db-execute 打到一个从未开通妙搭的租户,返回 221800 miaoda UAT not activated,hint 却是「verify table/column names… target the dev database with --environment dev」——两条都没用,agent 照做只会在查表和换环境之间空转,且每次都以同样的错误告终。

withAppsHint 的 hint 是命令级的("verify --app-id"、"verify table/column names"、"list releases"),描述的都是请求;而 failed_precondition 的定义恰恰是「请求合法、但系统状态不满足,调用方要去改状态而不是重试」。给它套请求形状的建议,是构造性错误。

所以:登记 221800validation/failed_precondition,并把 hint 兜底按 subtype 门控。

影响面是 2 个码,因为该 subtype 在本域只覆盖这些:

  • 221800 —— 现在不填 hint,message 和 code 仍承载语义;
  • 400002655「无运行容器」—— 仅当它流到非可观测命令时;可观测命令由 withObservabilityHint 先改写,而「verify --app-id」对一个没部署的 app 本来就不是解法。

其余 precondition 码到不了门控:400002465/500002759 被上方 isAppNoDatabaseError 分支拦截,400002479withDBSyncHint 处理且不委派到这里。另外 78 个调用点在任何输入下都走原路径。

只按这一个 subtype 门控、不按 Category:本包刻意断言了 +role-list 的认证失败(99991663)要保留 app-access hint、credential 签发的 503 要保留 developer-access hint。那些 hint 措辞足够宽,套在「调用方身份」类失败上仍成立;只有 precondition 这一类是构造性地被描述错。

2.2 登记 5 个 file / app 级业务码

这些码此前全部落 api/unknown + exit 1——调用方分不出「app id 写错」「没权限」「上游抖了」,退出码也无法分流:

code 分类 退出码
400002484 app not found validation/invalid_argument 2
400002467 / 500002761 无 admin/developer 权限 authorization/permission_denied 3
400000034 / 500000034 文件不存在 api/not_found 1

三点说明:

  • 400002467 不是 file 专属:db 命令(+db-table-list / -table-get / -quota-get / -changelog-list)打无权限 app 也返回它,所以登记一次同时修好两个域。
  • 400002484invalid_argument 而非 not_found:它对格式非法的 id(notanappidapp_1)同样返回,说明失败在入参本身,而不是「资源存在与否」。归为 validation 后退出码是 2,把「你传的 id 不对」与「上游故障」分开。
  • 新旧号都登记:这批码正在从 5xxxxxxxx 段迁到 4xxxxxxxx 段,迁移按环境分批推进,两个号同时活着;只留新号会让尚未升级的环境退回 api/unknown——这正是服务端换号那次把 no-database 恢复流程静默搞没的坑(见 appNoDatabaseCode 的注释)。号也不能按前缀推算500002761 迁移后是 400002467,尾号一起变了。

未加任何域内 hint:permission_denied 已有框架层的恢复文案,再写一条域内专属的就得猜 remedy,而「猜出来的 hint」正是 2.1 要修的那类问题。


验证

  • 单测internal/errclassshortcuts/appsshortcuts/apps/gitcredinternal/outputerrscmd 全绿。
  • 新增用例:4 组 hint 用例 + 6 行码表断言。其中一条守的是 withAppsHint 内部两个分支的先后顺序——400002465「app 无数据库」本身就是 failed_precondition,它的 message 改写与专属 hint 必须继续优先于新加的门控,否则这条恢复流程会被门控吞掉。
  • 反向锁:另有用例断言 api/unknownapi/server_errorauthentication/token_invalid 等类别仍然照常填充命令级 hint,防止后来者把门控从 subtype 扩大到 Category、破掉本包已有的两条刻意契约。

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling and classification of Spark errors involving inactive tenants, invalid app IDs, inaccessible files, and insufficient permissions.
    • Prevented misleading app hints from appearing for failed-precondition errors while preserving existing hints and overrides.
  • Safety

    • Added stronger confirmation and dry-run safeguards before clearing an entire environment cache.
    • Clarified cache deletion results and verification steps.
  • Documentation

    • Updated cache management guidance to require explicit environment selection and confirmation before destructive actions.

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9aedc463-e254-4f50-91de-290c20b34e86

📥 Commits

Reviewing files that changed from the base of the PR and between 72e6384 and 9fb2f5d.

📒 Files selected for processing (3)
  • internal/errclass/codemeta_spark.go
  • internal/errclass/codemeta_spark_test.go
  • shortcuts/apps/apps_hints_more_test.go

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The change adds Spark error metadata, filters generic app hints for failed-precondition errors, and strengthens cache-clearing confirmation guidance.

Changes

Error classification and app hints

Layer / File(s) Summary
Spark error metadata and coverage
internal/errclass/codemeta_spark.go, internal/errclass/codemeta_spark_test.go
Adds validation, not-found, and permission-denied metadata for Spark error codes and tests the classifications.
App hint filtering and regression coverage
shortcuts/apps/common.go, shortcuts/apps/apps_hints_more_test.go
Suppresses generic hints for failed-precondition errors while preserving existing hints, causes, overrides, and classifications. Tests cover these cases and other error subtypes.

Cache operation safeguards

Layer / File(s) Summary
Cache confirmation and deletion guidance
skills/lark-apps/SKILL.md, skills/lark-apps/references/lark-apps-cache.md
Requires confirmation or dry-run handling before environment-wide cache clearing. Documents deletion counts, verification, environment selection, and retry rules.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 9fb2f

This PR clarifies cache-clear confirmation behavior and classifies app failures for more reliable caller decisions; no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

  • larksuite/cli#2162: Both changes modify withAppsHint and its preservation of existing overrides.
  • larksuite/cli#2217: Both changes modify app hint behavior and Spark error metadata.
  • larksuite/cli#2361: Contains related cache-clear safeguards and failed-precondition hint handling.

Suggested reviewers: liangshuo-1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the motivation, changes, and verification, but it does not use the template headings or explicitly state related issues.
Title check ✅ Passed The title is concise and accurately summarizes both primary changes: cache-clear confirmation and classifiable apps failures.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/apps-error-contract-cache-confirm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@9fb2f5d8093ee3381b93977607b79db9663d6779

🧩 Skill update

npx skills add larksuite/cli#fix/apps-error-contract-cache-confirm -y -g

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.41%. Comparing base (755daa4) to head (9fb2f5d).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2415      +/-   ##
==========================================
- Coverage   76.41%   76.41%   -0.01%     
==========================================
  Files        1047     1047              
  Lines      115504   115506       +2     
==========================================
  Hits        88260    88260              
- Misses      20443    20444       +1     
- Partials     6801     6802       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Asked to clear an app's online cache, an agent read `Risk: high-risk-write` from
--help and then supplied `--yes` itself on the first call, wiping production
cache without ever hitting the confirmation gate.

The CLI gate is fine: no --yes -> exit 10 confirmation_required, and --dry-run ->
exit 0 without triggering it. The wording was not. It only forbade appending
`--yes` *after* an exit-10, and said "已明确授权可直接带 --yes" without defining
authorization — so "clear my cache" read as authorization.

- `+cache-clear` gets a CAUTION block: never self-supply `--yes` on the first
  call; without confirmation, either --dry-run or ask, then stop and wait. exit
  10 is not a signal to retry with --yes.
- Add a zero-ambiguity table separating a *request* to clear ("clear the online
  cache") from a *confirmation* ("我确认清 dev"), so blocking the accidental wipe
  does not also kill the cases that were already correct: an explicit
  confirmation still goes straight to `--yes`, and a request with no environment
  named still has to ask instead of picking one.
- Note that online needs a confirmation phrase even when named explicitly.

`+cache-delete` gains the response field an agent has to read
(`deleted_key_count`): 0 means the key never existed, not "deleted
successfully", plus the get -> delete -> get chain needed to prove a delete took
effect — a single miss afterwards cannot tell the two apart.

SKILL.md: add +cache-clear to 禁止预授权判定底线, the one list a pre-authorized
run cannot skip; a reference-level rule alone would be bypassed there. The
routing table is left alone — no other row annotates risk, including
+file-delete, +role-delete and +member-remove.
@chenxingyang1019
chenxingyang1019 force-pushed the fix/apps-error-contract-cache-confirm branch from a968233 to 00106cf Compare August 20, 2026 06:47
@CLAassistant

CLAassistant commented Aug 20, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@chenxingyang1019
chenxingyang1019 force-pushed the fix/apps-error-contract-cache-confirm branch from 00106cf to 72e6384 Compare August 20, 2026 07:22
`+db-execute` against a tenant that never activated Miaoda returns code 221800
"miaoda UAT not activated" with the hint "verify table/column names with
`+db-table-get` ... target the dev database with --environment dev". Neither step
can help: the failure is tenant-level, so a caller following the hint loops over
table lookups and env retries that fail identically.

Two causes. 221800 was unregistered, so it degraded to api/unknown — nothing in
the envelope distinguished "your tenant is not activated, stop" from "your SQL
was wrong, fix it and retry". And withAppsHint filled the caller's hint whenever
the server sent none, without looking at what failed: the hints are
command-scoped ("verify --app-id", "verify table/column names", "list releases"),
so every one of them describes the request, and the request is exactly what
failed_precondition says was fine.

Register 221800 as validation/failed_precondition (same shape as 400002465 "app
has no database yet") and gate the hint fallback on the subtype.

Blast radius is two codes, since that is all the subtype covers here:
  - 221800 — now withheld; message and code still carry the meaning.
  - 400002655 "no running container" — only when it reaches a non-observability
    command; the observability pair rewrites it first, and "verify --app-id" was
    never the fix for an undeployed app.
400002465 / 500002759 are intercepted by the isAppNoDatabaseError branch above
the gate, and 400002479 is served by withDBSyncHint, which does not delegate
here. The other 78 call sites take the original path for every input.

Gate on the one subtype, not on Category: this package asserts on purpose that an
authentication failure on +role-list (99991663) keeps the app-access hint and a
503 on credential issuance keeps the developer-access hint. Those hints are broad
enough to survive a caller-standing failure; only the precondition class is
misdescribed by construction. A test pins that, so widening the gate to Category
fails loudly instead of silently dropping those hints.

The gate is asserted on the real classification path (BuildAPIError -> the code
table -> withAppsHint), not only on a hand-built Problem. Constructing
SubtypeFailedPrecondition directly feeds the gate the input it wants and passes
whether or not 221800 is registered, so the registration itself has to be part of
what the test covers.

No recovery hint for 221800 — the activation path is a product procedure, and
guessing one is what made this failure misleading in the first place.
Five Spark business codes reached the CLI unregistered, so every one of them came
out as api/unknown with exit 1: a caller could not tell "your app id is wrong"
from "you lack permission" from "the upstream is having a bad minute", and the
exit code offered no way to branch either.

  400002484  app not found            -> validation/invalid_argument     exit 2
  400002467  no admin/developer perm  -> authorization/permission_denied exit 3
  500002761  ditto, pre-4xx renumber  -> same
  400000034  file not found/no access -> api/not_found                   exit 1
  500000034  ditto, pre-4xx renumber  -> same

400002467 is not file-specific: db commands (+db-table-list, +db-table-get,
+db-quota-get, +db-changelog-list) return it for an app the caller cannot access,
so registering it fixes both domains at once.

400002484 covers a well-formed id that does not exist AND a malformed one
("notanappid", "app_1" return it too), so the argument itself is the failure ->
invalid_argument, whose exit 2 separates "you passed the wrong id" from an
upstream fault. Environments that have not picked it up answer with 400002465
instead, conflating it with "app has no database yet"; the CLI cannot tell those
apart on the old code, so nothing here keys on that.

Both the current and the pre-4xx number are registered for each file failure.
The domain is moving its client-class errors from the 5xxxxxxxx band into
4xxxxxxxx, rolled out per environment, so both are live at once and dropping the
old one would silently return the un-migrated half to api/unknown — the same trap
that made the no-database recovery flow disappear when the server renumbered it
(see appNoDatabaseCode). The new number is not derivable from the old either:
500002761 became 400002467, tail digits included.

No hints added: permission_denied already has framework recovery wording, and a
domain-specific one would have to invent a remedy.
@chenxingyang1019
chenxingyang1019 force-pushed the fix/apps-error-contract-cache-confirm branch from 72e6384 to 9fb2f5d Compare August 20, 2026 08:04
@chenxingyang1019
chenxingyang1019 merged commit ca35f60 into main Aug 20, 2026
31 of 42 checks passed
@chenxingyang1019
chenxingyang1019 deleted the fix/apps-error-contract-cache-confirm branch August 20, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants