forge-app-review

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Forge App Review

Forge应用审查

Deep pre-deploy review of Forge apps across Security, Architecture, Cost, Performance, and Triggers & Scheduling. Produces a severity-sorted issue list with actionable fixes.
对Forge应用进行深度预部署审查,涵盖安全架构成本性能以及触发器与调度五大维度。生成按严重程度排序的问题列表,并提供可执行的修复方案。

Forge Pricing Reference

Forge定价参考

Forge uses a consumption-based pricing model. Charges only apply above free monthly allowances. Use this table to assess cost impact of findings:
CapabilityBilling UnitFree Monthly AllowanceOverage Price (USD)
Functions: DurationGB-seconds100,000 GB-seconds$0.000025 / GB-second
KVS: ReadsGB read0.1 GB$0.055 / GB
KVS: WritesGB written0.1 GB$1.090 / GB
Logs: WritesGB written1 GB$1.005 / GB
SQL: Compute durationHours1 hour$0.143 / hour
SQL: Compute requestsPer 1M requests100,000 requests$1.929 / 1M requests
SQL: Data storedGB-hours730 GB-hours$0.00076850 / GB-hour
Key cost insight: KVS writes are ~20× more expensive than reads. Logging is ~$1/GB over the free tier. The cost formula for functions is: GB-seconds = (memoryMiB ÷ 1024) × duration in seconds.
Free capabilities (not billed): UI modules (UI Kit and Custom UI frontends run in the browser), Jira expressions, Forge Remote invocations (though the remote function runtime is billed), entity properties (stored by the product, not by Forge Storage).

Forge采用基于用量的定价模式,仅在超出每月免费额度时产生费用。使用下表评估审查发现的成本影响:
功能项计费单位每月免费额度超额价格(美元)
Functions: DurationGB-seconds100,000 GB-seconds$0.000025 / GB-second
KVS: ReadsGB read0.1 GB$0.055 / GB
KVS: WritesGB written0.1 GB$1.090 / GB
Logs: WritesGB written1 GB$1.005 / GB
SQL: Compute durationHours1 hour$0.143 / hour
SQL: Compute requestsPer 1M requests100,000 requests$1.929 / 1M requests
SQL: Data storedGB-hours730 GB-hours$0.00076850 / GB-hour
关键成本洞察:KVS写入成本约为读取的20倍。超出免费额度后,日志写入成本约为1美元/GB。函数成本计算公式为:GB-seconds = (memoryMiB ÷ 1024) × 运行时长(秒)
免费功能(不计费):UI模块(UI Kit和Custom UI前端在浏览器中运行)、Jira表达式、Forge Remote调用(但远程函数运行时会计费)、实体属性(由产品存储,不占用Forge Storage配额)。

Execution Mandate

执行要求

When triggered, immediately:
  1. Read
    manifest.yml
    — this is the source of truth for permissions, modules, egress, triggers, scheduled triggers, and function memory settings
  2. Read
    package.json
    — check dependencies, versions, scripts
  3. Scan all resolver files in
    src/
    — check patterns, error handling, data flow, API call patterns (N+1, missing fields, sequential calls), logging verbosity
  4. Scan UI code (Custom UI or UI Kit) — check component patterns, bridge usage, whether API calls and logic could be moved to the frontend, product context usage, invoke patterns (chatty, per-render)
  5. Check for Forge Storage / Entity Store usage patterns — TTL strategy, write frequency, query vs iteration, entity properties vs KVS
  6. Check trigger and scheduling configuration — frequency, filtering, ignoreSelf, early exit, polling vs event-driven
  7. Check for Forge Remote usage or opportunities — compute offloading, trade-offs
  8. Compile all findings into a severity-sorted issue list
Do NOT ask the user what to review. Review everything. Do NOT modify any code unless explicitly asked. Do NOT skip categories — even if the app looks clean, confirm it explicitly.

触发审查后,立即执行以下步骤:
  1. 读取
    manifest.yml
    — 这是权限、模块、出站访问、触发器、调度触发器以及函数内存设置的权威来源
  2. 读取
    package.json
    — 检查依赖项、版本、脚本
  3. 扫描
    src/
    目录下所有resolver文件 — 检查代码模式、错误处理、数据流、API调用模式(N+1问题、缺失字段、顺序调用)、日志冗余度
  4. 扫描UI代码(Custom UI或UI Kit) — 检查组件模式、bridge使用方式、API调用和逻辑是否可迁移至前端、产品上下文使用、invoke模式(频繁调用、每次渲染调用)
  5. 检查Forge Storage / Entity Store使用模式 — TTL策略、写入频率、查询与迭代对比、实体属性与KVS选择
  6. 检查触发器和调度配置 — 频率、过滤规则、ignoreSelf设置、提前退出机制、轮询与事件驱动对比
  7. 检查Forge Remote的使用情况或适用场景 — 计算卸载、权衡利弊
  8. 将所有审查发现整理为按严重程度排序的问题列表
请勿询问用户需要审查哪些内容,需全面审查所有维度。除非用户明确要求,否则请勿修改任何代码。请勿跳过任何分类 — 即使应用看起来无问题,也需明确确认。

Review Process

审查流程

Step 1: Manifest Analysis (
manifest.yml
)

步骤1:Manifest分析(
manifest.yml

Read the manifest first. Extract:
  • Permissions/scopes — list all
    scopes
    and
    permissions
    entries
  • Modules — list all module types and their function key references
  • Egress — check
    app.connect.remotes
    or
    permissions.external.fetch.backend
    URLs
  • Environment variables — check for
    app.storage
    or environment variable declarations
Cross-reference every
function
key in modules against actual resolver
resolver.define()
calls.
优先读取manifest文件,提取以下信息:
  • 权限/范围 — 列出所有
    scopes
    permissions
    条目
  • 模块 — 列出所有模块类型及其关联的function密钥
  • 出站访问 — 检查
    app.connect.remotes
    permissions.external.fetch.backend
    中的URL
  • 环境变量 — 检查
    app.storage
    或环境变量声明
将模块中的每个
function
密钥与实际resolver的
resolver.define()
调用进行交叉验证。

Step 2: Dependencies (
package.json
)

步骤2:依赖项检查(
package.json

Check:
  • Node.js engine compatibility (Forge requires Node 18.x+)
  • Unnecessary large dependencies (e.g.,
    lodash
    when only one function is used,
    moment
    instead of native Date or
    dayjs
    )
  • Missing
    @forge/api
    ,
    @forge/bridge
    , or
    @forge/ui
    depending on app type
  • Dev dependencies leaking into production
  • Outdated
    @forge/*
    packages
检查以下内容:
  • Node.js引擎兼容性(Forge要求Node 18.x+)
  • 不必要的大型依赖项(例如仅使用一个函数却引入
    lodash
    ,使用
    moment
    而非原生Date或
    dayjs
  • 根据应用类型,是否缺失
    @forge/api
    @forge/bridge
    @forge/ui
    依赖
  • 开发依赖项泄漏至生产环境
  • @forge/*
    包版本过时

Step 3: Resolver Code

步骤3:Resolver代码检查

Read all files that contain
resolver.define
or
Resolver
imports. Check for:
  • Error handling patterns
  • API call patterns (
    requestJira
    ,
    requestConfluence
    ,
    api.asUser()
    ,
    api.asApp()
    )
  • Data validation and sanitization
  • Storage operations
  • External fetch calls
读取所有包含
resolver.define
Resolver
导入的文件,检查:
  • 错误处理模式
  • API调用模式(
    requestJira
    requestConfluence
    api.asUser()
    api.asApp()
  • 数据验证与清理
  • 存储操作
  • 外部fetch调用

Step 4: UI Code

步骤4:UI代码检查

For UI Kit apps — scan for
@forge/react
imports, component usage, hooks. For Custom UI apps — scan for
@forge/bridge
usage,
invoke()
calls, CSP compliance.
In both cases, check for:
  • Frontend offloading opportunities: Are there resolvers that only do read-only
    requestJira()
    /
    requestConfluence()
    calls that could instead use
    @forge/bridge
    directly from the browser?
  • Product context via resolver: Is the app invoking a resolver just to get issue/project/space key? Use
    useProductContext()
    (UI Kit) or
    view.getContext()
    (Custom UI) instead.
  • Invoke on every render: Is
    invoke()
    called without proper
    useEffect
    with empty dependency array, causing re-invocation on every render?
  • Client-side logic: Is data formatting, sorting, filtering, or validation done in a resolver when it could run in the browser for free?
对于UI Kit应用 — 扫描
@forge/react
导入、组件使用、钩子函数。 对于Custom UI应用 — 扫描
@forge/bridge
使用、
invoke()
调用、CSP合规性。
两种场景下均需检查:
  • 前端卸载机会:是否存在仅执行只读
    requestJira()
    /
    requestConfluence()
    调用的resolver,可改为直接在浏览器中使用
    @forge/bridge
  • 通过resolver获取产品上下文:是否仅为获取issue/project/space密钥而调用resolver?UI Kit应用应使用
    useProductContext()
    ,Custom UI应用应使用
    view.getContext()
    替代。
  • 每次渲染调用invoke:是否在未添加空依赖数组的
    useEffect
    中调用
    invoke()
    ,导致每次渲染都重新调用?
  • 客户端逻辑:数据格式化、排序、过滤或验证是否在resolver中执行,而这些操作本可在浏览器中免费运行?

Step 5: Storage Analysis

步骤5:存储分析

Search for usage of:
  • storage.get
    ,
    storage.set
    ,
    storage.delete
    — check for unnecessary writes, short TTLs (KVS writes are ~20× more expensive than reads), and missing caching patterns
  • storage.query
    (Entity Store) — check for proper use of indexes,
    .where()
    , and
    .limit()
    instead of fetching all items and filtering in code
  • Entity properties (
    requestJira
    to
    /properties/
    ) — note these are free and stored by the product (not Forge Storage quota), suitable for small per-entity metadata (max 32 KB per property). Good for flags, markers, timestamps attached to Jira issues or Confluence pages. Queryable via JQL for Jira entity properties. Not suitable for sensitive data — visible to other apps and users via REST API.
  • Cache patterns — is app-level data that rarely changes (e.g., custom field IDs, project configs, workflow statuses) being fetched from APIs on every invocation? Should be cached in KVS with a TTL (1 hour+ preferred to minimize writes)
  • Write amplification — a 1-minute TTL cache with 100 calls/hour causes ~60 writes/hour; a 1-hour TTL causes ~1 write/hour at ~60× less cost
搜索以下用法:
  • storage.get
    storage.set
    storage.delete
    — 检查不必要的写入、过短的TTL(KVS写入成本约为读取的20倍)、缺失的缓存模式
  • storage.query
    (Entity Store) — 检查是否正确使用索引、
    .where()
    .limit()
    ,而非获取所有数据后在代码中过滤
  • 实体属性(通过
    requestJira
    调用
    /properties/
    ) — 注意这些是免费的,由产品存储(不占用Forge Storage配额),适用于小型实体元数据(每个属性最大32 KB)。适合存储Jira问题或Confluence页面的标记、状态、时间戳。Jira实体属性可通过JQL查询。不适用于敏感数据 — 其他应用和用户可通过REST API查看。
  • 缓存模式 — 对于极少变化的应用级数据(例如自定义字段ID、项目配置、工作流状态),是否每次调用都从API获取?应缓存到KVS并设置TTL(推荐1小时以上,以减少写入次数)
  • 写入放大 — 1分钟TTL的缓存每小时被调用100次,会产生约60次写入;1小时TTL的缓存每小时仅产生约1次写入,成本降低约60倍

Step 6: Trigger & Scheduling Analysis

步骤6:触发器与调度分析

Check the manifest for
scheduledTrigger
and
trigger
modules:
  • Scheduled triggers: Is the interval appropriate? (
    fiveMinutes
    is rarely justified — prefer
    hour
    ,
    day
    , or
    week
    )
  • Polling vs events: Is a scheduled trigger polling for changes that could be caught by a product event trigger?
  • Event filtering: Do product event triggers have
    filter.expression
    to limit invocations to relevant events?
  • ignoreSelf: If the app writes to entities and listens to events on those entities, is
    filter.ignoreSelf: true
    set? (Jira only)
  • Early exit: Do trigger handler functions check for work to do before running expensive operations?
  • External polling: Could scheduled triggers polling external services be replaced with web triggers?
检查manifest中的
scheduledTrigger
trigger
模块:
  • 调度触发器:时间间隔是否合理?(
    fiveMinutes
    很少有必要 — 优先选择
    hour
    day
    week
  • 轮询vs事件驱动:调度触发器是否在轮询变化,而这些变化可通过产品事件触发器捕获?
  • 事件过滤:产品事件触发器是否设置
    filter.expression
    以限制仅调用相关事件?
  • ignoreSelf:如果应用写入实体并监听这些实体的事件,是否设置了
    filter.ignoreSelf: true
    ?(仅适用于Jira)
  • 提前退出:触发器处理函数是否在执行昂贵操作前检查是否有实际工作要做?
  • 外部轮询:调度触发器轮询外部服务是否可替换为web触发器?

Step 7: Forge Remote Analysis

步骤7:Forge Remote分析

Check if the app uses Forge Remote (
remotes:
section in manifest):
  • If present, note that Forge Remote offloads compute to an external backend — the Forge function is not executed for those calls, saving FaaS invocations. But the app loses "Runs on Atlassian" eligibility.
  • If not present, check whether the app would benefit from Forge Remote:
    • Compute-intensive operations (ML inference, image processing, complex report generation)
    • Long-running operations that approach the 25-second function timeout
    • Existing backend services the app duplicates logic from
    • Large-scale storage needs exceeding Forge Storage limits
  • Note: For most apps, staying on-platform is simpler. Only recommend Forge Remote when there's a genuine need.
检查应用是否使用Forge Remote(manifest中的
remotes:
部分):
  • 如果已使用,需注意Forge Remote将计算卸载至外部后端 — Forge函数不会为这些调用执行,节省FaaS调用次数,但应用将失去「Runs on Atlassian」资格。
  • 如果未使用,检查应用是否可从Forge Remote获益:
    • 计算密集型操作(ML推理、图像处理、复杂报告生成)
    • 接近25秒函数超时的长时间运行操作
    • 应用重复了现有后端服务的逻辑
    • 存储需求超出Forge Storage限制
  • 注意:对于大多数应用,留在平台内更简单。仅当确实有需求时才推荐使用Forge Remote。

Step 8: Compile Findings

步骤8:整理审查发现

Produce a single issue list sorted: Critical → Warning → Info.

生成单一问题列表,按严重→警告→信息排序。

Security Checks

安全检查

IDCheckSeverityWhat to Look For
SEC-01Overly broad scopesCritical
read:jira-work
when only
read:jira-work:jira
(granular) is needed. Any
write:
scope that isn't actually used in code. Any
manage:
or
admin:
scope.
SEC-02Missing egress restrictionsCriticalExternal
fetch()
calls in resolvers without matching
permissions.external.fetch.backend
entries in manifest. Wildcard egress domains (
*
).
SEC-03Hardcoded secretsCriticalAPI keys, tokens, passwords, or credentials in source code instead of using Forge environment variables (
process.env.FORGE_*
or
getAppEnvironmentVariable()
).
SEC-04Missing input sanitizationCriticalUser-provided data passed directly to API calls, storage keys, or rendered in Custom UI without sanitization. SQL/NoSQL injection patterns in storage queries.
SEC-05Unsafe Custom UI CSPWarning
unsafe-inline
,
unsafe-eval
, or overly broad
connect-src
in Custom UI resource configuration.
SEC-06Missing auth checksWarningResolvers that don't verify user context before performing write operations. Missing
context.accountId
validation.
SEC-07Sensitive data in storageWarningPII, tokens, or credentials stored in Forge Storage without encryption or with overly broad access.
SEC-08Excessive permissionsWarning
permissions.scopes
includes scopes not referenced by any API call in the codebase. Every scope should have a matching API usage.
SEC-09Classic scopes usedInfoUsing classic (coarse-grained) scopes like
read:jira-work
instead of granular scopes like
read:jira-work:jira
. Granular scopes are preferred.
SEC-10Unnecessary asApp() usageWarningUsing
api.asApp()
or
asApp()
for API calls in UI-triggered resolvers when
asUser()
would suffice.
asApp()
bypasses user permission checks — users can access data beyond their entitlements. Only use
asApp()
when there is no user context (scheduled triggers, web triggers) or when the API requires app-level auth (e.g., App Storage API).

ID检查项严重程度检查要点
SEC-01权限范围过宽严重仅需
read:jira-work:jira
(细粒度)权限时,却使用了
read:jira-work
权限。任何未在代码中实际使用的
write:
权限范围。任何
manage:
admin:
权限范围。
SEC-02缺失出站访问限制严重Resolver中存在外部
fetch()
调用,但manifest中未匹配
permissions.external.fetch.backend
条目。使用通配符出站域名(
*
)。
SEC-03硬编码密钥严重源代码中包含API密钥、令牌、密码或凭据,而非使用Forge环境变量(
process.env.FORGE_*
getAppEnvironmentVariable()
)。
SEC-04缺失输入清理严重用户提供的数据直接传入API调用、存储密钥或在Custom UI中渲染,未做清理。存储查询中存在SQL/NoSQL注入模式。
SEC-05不安全的Custom UI CSP警告Custom UI资源配置中存在
unsafe-inline
unsafe-eval
或过宽的
connect-src
SEC-06缺失身份验证检查警告Resolver在执行写入操作前未验证用户上下文。缺失
context.accountId
验证。
SEC-07存储中包含敏感数据警告Forge Storage中存储了PII、令牌或凭据,未加密或访问权限过宽。
SEC-08权限过度警告
permissions.scopes
包含代码库中任何API调用都未引用的权限范围。每个权限范围都应匹配实际API使用场景。
SEC-09使用经典权限范围信息使用
read:jira-work
等经典(粗粒度)权限范围,而非
read:jira-work:jira
等细粒度权限范围。优先使用细粒度权限范围。
SEC-10不必要的asApp()使用警告UI触发的resolver中使用
api.asApp()
asApp()
进行API调用,而
asUser()
已足够。
asApp()
会绕过用户权限检查 — 用户可访问超出其权限的数据。仅当无用户上下文(调度触发器、web触发器)或API需要应用级身份验证(例如App Storage API)时才使用
asApp()

Architecture Checks

架构检查

IDCheckSeverityWhat to Look For
ARC-01Function key mismatchCritical
resolver.define('functionName')
doesn't match the
function
key in
manifest.yml
modules.
ARC-02Monolithic resolverWarningSingle resolver file with 5+
resolver.define()
calls handling unrelated functionality. Split into separate files by domain.
ARC-03Missing error handlingWarning
resolver.define()
callbacks without try-catch. API calls (
requestJira
,
requestConfluence
) without
.catch()
or error status checks.
ARC-04Incorrect API usageWarningUsing
api.asApp()
when
api.asUser()
is appropriate (or vice versa). Missing
response.json()
parsing. Not checking
response.ok
or
response.status
.
ARC-05Module type mismatchWarningUsing a
jira:issuePanel
module for content that should be a
jira:issueGlance
or
jira:issueContext
. Module type should match UX intent.
ARC-06Missing resolver validationWarningResolver accepts payload from UI but doesn't validate shape/types before processing.
ARC-07Poor code organizationInfoAll code in a single file. No separation between API logic, business logic, and data access.
ARC-08Unused modulesInfoModules defined in
manifest.yml
that have no corresponding UI or resolver implementation.
ARC-09Missing TypeScriptInfoJavaScript used instead of TypeScript. TypeScript catches many issues at build time.
ARC-10Forge Remote trade-offs not consideredInfoApp uses Forge Remote (
remotes:
in manifest) but may not need it — Forge Remote makes the app ineligible for "Runs on Atlassian" status and requires operating your own infrastructure (patching, uptime, incident response). Only use when on-platform capabilities are genuinely insufficient (compute-intensive tasks, >25s timeout needs, existing backend integration, storage limits exceeded).

ID检查项严重程度检查要点
ARC-01Function密钥不匹配严重
resolver.define('functionName')
manifest.yml
模块中的
function
密钥不匹配。
ARC-02单体Resolver警告单个resolver文件包含5个以上
resolver.define()
调用,处理不相关功能。应按领域拆分为多个文件。
ARC-03缺失错误处理警告
resolver.define()
回调函数未使用try-catch。API调用(
requestJira
requestConfluence
)未使用
.catch()
或错误状态检查。
ARC-04API使用错误警告应使用
api.asUser()
时却使用了
api.asApp()
(反之亦然)。缺失
response.json()
解析。未检查
response.ok
response.status
ARC-05模块类型不匹配警告使用
jira:issuePanel
模块承载本应属于
jira:issueGlance
jira:issueContext
的内容。模块类型应符合UX意图。
ARC-06缺失Resolver验证警告Resolver接受UI传来的负载,但在处理前未验证其格式/类型。
ARC-07代码组织混乱信息所有代码都在单个文件中。未分离API逻辑、业务逻辑和数据访问层。
ARC-08未使用的模块信息
manifest.yml
中定义的模块无对应的UI或resolver实现。
ARC-09未使用TypeScript信息使用JavaScript而非TypeScript。TypeScript可在构建时捕获许多问题。
ARC-10未考虑Forge Remote的权衡信息应用使用了Forge Remote(manifest中的
remotes:
)但可能并无必要 — Forge Remote会使应用失去「Runs on Atlassian」资格,且需要自行维护基础设施(补丁、可用性、事件响应)。仅当平台内能力确实不足时才使用(计算密集型任务、超过25秒超时需求、现有后端集成、存储限制超出)。

Cost Checks

成本检查

IDCheckSeverityWhat to Look For
CST-01Chatty resolversWarningUI making multiple
invoke()
calls on load when data could be batched into a single resolver call. Each invocation counts toward Forge function invocation limits and GB-seconds.
CST-02No pagination / missing maxResultsWarningProduct API calls (e.g., search issues, get pages) without
maxResults
/
limit
parameters or pagination handling. Fetching default page sizes (often 50–100) when only a few results are needed wastes bandwidth and function duration. Always pass explicit
maxResults
matched to actual need. Conversely, when you DO need all results, use the maximum allowed page size (e.g.,
maxResults=100
for Jira search) to minimize the number of paginated round-trips — fewer requests means shorter function duration.
CST-03Unnecessary storage opsWarningReading the same storage key multiple times in a single invocation. Writing to storage on every invocation when data hasn't changed. KVS writes are ~20× more expensive than reads — minimize write frequency. Short TTL caches (e.g., 1-minute TTL) cause excessive writes; prefer longer TTLs (1 hour+) where data allows.
CST-04Bloated bundleWarningDependencies that significantly increase bundle size:
lodash
(use
lodash-es
or individual imports),
moment
(use
dayjs
or
date-fns
),
axios
(use native
fetch
).
CST-05Redundant API callsWarningFetching the same data from product APIs multiple times in one resolver execution. Cache results in variables.
CST-06Logic in resolver that could run client-sideWarningData formatting, sorting, filtering, validation, or transformation done in a resolver when it could run in the browser for free. UI Kit and Custom UI frontends run entirely in the browser and are not subject to function invocation costs. Look for resolvers that only reshape data — move that logic to the frontend.
CST-07Resolver used for product contextWarningInvoking a resolver just to get the current issue key, project key, or space key. UI Kit apps should use
useProductContext()
from
@forge/react
; Custom UI apps should use
view.getContext()
from
@forge/bridge
. These provide context directly in the browser with no function invocation.
CST-08API calls via resolver instead of bridgeWarningUsing a Forge resolver/function to make read-only
requestJira()
or
requestConfluence()
calls when the same call could be made directly from the frontend using
@forge/bridge
. Both UI Kit and Custom UI can call
requestJira()
/
requestConfluence()
directly from the browser — no function invocation needed. Only keep API calls in resolvers when they require
asApp()
context, access secrets, or perform sensitive operations.
CST-09Resolver called on every renderWarningCalling
invoke()
inside a component render body or in a
useEffect
without an empty dependency array, causing repeated function invocations on every re-render. Fetch once on mount and store the result in component state.
CST-10N+1 API callsWarningFetching a list of items then making a separate API call for each item to get details. Use bulk endpoints instead:
POST /rest/api/3/issue/bulkfetch
for issues,
GET /rest/api/3/user/bulk
for users,
POST /rest/api/3/search
with
fields
parameter for search+details in one call.
CST-11Missing field selection on API callsWarningAPI calls (especially search/list endpoints) that don't specify a
fields
parameter, fetching all fields when only a few are needed. Always specify
fields=summary,status,assignee
(etc.) to reduce response payload size, bandwidth, and function duration.
CST-12Verbose logging in hot pathsWarning
console.log()
with large payloads (e.g.,
JSON.stringify(event)
) in high-frequency functions like product event triggers or popular resolvers. Log writes are billable at $1.005/GB over the 1 GB free tier. Log only errors and meaningful state changes in production. Use conditional logging gated behind an environment variable for debug output.
CST-13Large resolver payloadsInfoResolver returning more data than the UI needs. Trim response objects to only include fields the UI consumes.
CST-14Unused dependenciesInfoPackages in
dependencies
that are not imported anywhere in the source code.
CST-15Memory over-provisioningInfoFunction
memoryMiB
in manifest set higher than needed. Default is 256 MB. Simple resolvers doing a single API call often work fine with 128 MB. Cost formula: GB-seconds = (memoryMiB ÷ 1024) × duration. Halving memory halves cost per second. Check for
memoryMiB: 512
or
1024
on lightweight functions. Note: more memory can also improve CPU allocation, which may reduce duration enough to offset the higher per-second cost — profile before reducing.
CST-16Entity properties not used for free storageInfoApp stores small per-entity metadata (flags, timestamps, status markers) in Forge KVS when Jira entity properties or Confluence content properties could be used instead. Entity properties are free (stored by the product, no Forge Storage quota), travel with the entity during export/import, and Jira entity properties are queryable via JQL. Max 32 KB per property. Not suitable for sensitive data (visible via REST API).
CST-17Pre-filtering not pushed to API layerWarningResolver fetches all items from an API and filters in code (e.g., fetching all issues then checking status in a loop). Push filtering to the API layer: use JQL conditions (
status = Done AND assignee is EMPTY
), CQL, or API query parameters so only relevant items are returned. For scheduled jobs, use date-based filters (
updated >= "${lastRunDate}"
) to process only items changed since the last run. The real saving comes from doing less work, not from batching.
CST-18Polling external service instead of web triggerInfoScheduled trigger polls an external third-party service for updates. Consider replacing with a Forge web trigger — register the web trigger URL as a webhook with the external service so it calls your app only when something changes. Web trigger invocations have no flagfall or network cost, though function runtime is still billed. Eliminates all empty polling invocations.

ID检查项严重程度检查要点
CST-01频繁调用的Resolver警告UI在加载时多次调用
invoke()
,而这些数据可批量为一次resolver调用。每次调用都会占用Forge函数调用限额和GB-seconds。
CST-02无分页/缺失maxResults警告产品API调用(例如搜索问题、获取页面)未设置
maxResults
/
limit
参数或未处理分页。仅需少量结果时却获取默认页面大小(通常50–100),浪费带宽和函数运行时长。始终传入与实际需求匹配的显式
maxResults
。反之,当确实需要所有结果时,使用允许的最大页面大小(例如Jira搜索使用
maxResults=100
)以减少分页往返次数 — 请求越少,函数运行时长越短。
CST-03不必要的存储操作警告单次调用中多次读取相同存储密钥。数据未变化时却每次调用都写入存储。KVS写入成本约为读取的20倍 — 尽量减少写入频率。短TTL缓存(例如1分钟TTL)会导致过多写入;数据允许时优先选择更长的TTL(1小时以上)。
CST-04臃肿的包警告显著增加包大小的依赖项:
lodash
(使用
lodash-es
或单独导入)、
moment
(使用
dayjs
date-fns
)、
axios
(使用原生
fetch
)。
CST-05冗余API调用警告单个resolver执行中多次从产品API获取相同数据。将结果缓存到变量中。
CST-06Resolver中执行可客户端化的逻辑警告数据格式化、排序、过滤、验证或转换在resolver中执行,而这些操作本可在浏览器中免费运行。UI Kit和Custom UI前端完全在浏览器中运行,不受函数调用成本限制。寻找仅重塑数据的resolver — 将此类逻辑迁移至前端。
CST-07使用Resolver获取产品上下文警告仅为获取当前issue密钥、项目密钥或空间密钥而调用resolver。UI Kit应用应使用
@forge/react
中的
useProductContext()
;Custom UI应用应使用
@forge/bridge
中的
view.getContext()
。这些可直接在浏览器中提供上下文,无需调用函数。
CST-08通过Resolver而非bridge调用API警告使用Forge resolver/函数执行只读
requestJira()
requestConfluence()
调用,而相同调用可直接通过前端使用
@forge/bridge
完成。UI Kit和Custom UI均可直接在浏览器中调用
requestJira()
/
requestConfluence()
— 无需调用函数。仅当需要
asApp()
上下文、访问密钥或执行敏感操作时,才在resolver中保留API调用。
CST-09每次渲染调用Resolver警告在组件渲染体中或未添加空依赖数组的
useEffect
中调用
invoke()
,导致每次重新渲染都重复调用函数。在挂载时获取一次数据并存储到组件状态中。
CST-10N+1 API调用警告获取项目列表后,为每个项目单独调用API获取详情。使用批量端点替代:问题使用
POST /rest/api/3/issue/bulkfetch
,用户使用
GET /rest/api/3/user/bulk
,搜索+详情使用
POST /rest/api/3/search
并携带
fields
参数一次完成。
CST-11API调用未指定字段警告API调用(尤其是搜索/列表端点)未指定
fields
参数,仅需少数字段时却获取所有字段。始终指定
fields=summary,status,assignee
(等)以减少响应 payload 大小、带宽和函数运行时长。
CST-12高频路径中的冗余日志警告在高频函数(例如产品事件触发器或热门resolver)中使用
console.log()
输出大 payload(例如
JSON.stringify(event)
)。超出1GB免费额度后,日志写入成本为1.005美元/GB。生产环境中仅记录错误和有意义的状态变化。使用环境变量控制的条件日志输出调试信息。
CST-13Resolver返回过大Payload信息Resolver返回的数据超出UI实际需求。裁剪响应对象,仅保留UI需要的字段。
CST-14未使用的依赖项信息
dependencies
中的包未在源代码中任何地方导入。
CST-15内存过度配置信息manifest中设置的函数
memoryMiB
高于实际需求。默认值为256 MB。仅执行单个API调用的简单resolver通常使用128 MB即可。成本公式:GB-seconds = (memoryMiB ÷ 1024) × 运行时长。内存减半,每秒成本也减半。检查轻量级函数是否设置了
memoryMiB: 512
1024
。注意:更多内存也可提升CPU分配,可能缩短运行时长以抵消更高的每秒成本 — 调整前需先做性能分析。
CST-16未使用免费的实体属性存储信息应用将小型实体元数据(标记、时间戳、状态标记)存储在Forge KVS中,而本可使用Jira实体属性或Confluence内容属性替代。实体属性是免费的(由产品存储,不占用Forge Storage配额),在导出/导入时随实体迁移,且Jira实体属性可通过JQL查询。每个属性最大32 KB。不适用于敏感数据(可通过REST API查看)。
CST-17未将预过滤推至API层警告Resolver从API获取所有项目后在代码中过滤(例如获取所有问题后在循环中检查状态)。将过滤推至API层:使用JQL条件(
status = Done AND assignee is EMPTY
)、CQL或API查询参数,仅返回相关项目。对于调度任务,使用基于日期的过滤(
updated >= "${lastRunDate}"
)仅处理上次运行后变更的项目。真正的节省来自减少工作量,而非批量处理。
CST-18轮询外部服务而非使用web触发器信息调度触发器轮询外部第三方服务获取更新。考虑替换为Forge web触发器 — 将web触发器URL注册为外部服务的webhook,仅当发生变化时才调用应用。web触发器调用无额外成本,仅函数运行时计费。可消除所有空轮询调用。

Trigger & Scheduling Checks

触发器与调度检查

IDCheckSeverityWhat to Look For
TRG-01Excessive scheduled trigger frequencyWarningScheduled triggers using
interval: fiveMinutes
or
interval: hour
when the data they process changes less frequently. Ask: how often does the underlying data actually change? Prefer
day
or
week
intervals unless sub-hourly freshness is genuinely required.
TRG-02Polling instead of event triggersWarningScheduled triggers that poll for changes (e.g., checking if issues were updated) instead of using product event triggers (
avi:jira:updated:issue
, etc.) that fire only when the event actually occurs. Replace polling with event-driven triggers to eliminate empty invocations.
TRG-03Missing trigger event filtersWarningProduct event triggers without a
filter
expression in the manifest. Without filtering, the function is invoked for every matching event across the entire site. Use manifest-level
filter.expression
to restrict invocations to specific projects, issue types, or conditions.
TRG-04Missing ignoreSelf on triggersWarningApp that writes to Jira entities AND listens to events on those same entities, without
filter.ignoreSelf: true
in the manifest. This causes feedback loops where the app's own updates trigger its own handler. (Jira events only — not yet supported for Confluence.)
TRG-05No early exit in trigger handlerInfoTrigger handler functions that don't check whether there is real work to do before performing expensive operations. Add a lightweight guard at the top of the function (e.g., check a timestamp, check event fields) and return early if no action is needed.
TRG-06Polling external service instead of web triggerInfoScheduled trigger polling an external service for updates. Consider replacing with a Forge web trigger — register the web trigger URL as a webhook with the external service so it calls you only when something changes.

ID检查项严重程度检查要点
TRG-01调度触发器频率过高警告调度触发器使用
interval: fiveMinutes
interval: hour
,而其处理的数据变更频率更低。思考:底层数据实际多久变更一次?除非确实需要亚小时级的新鲜度,否则优先选择
day
week
间隔。
TRG-02使用轮询而非事件触发器警告调度触发器轮询变更(例如检查问题是否更新),而本可使用产品事件触发器(
avi:jira:updated:issue
等)仅在事件实际发生时触发。将轮询替换为事件驱动触发器,消除空调用。
TRG-03缺失触发器事件过滤警告产品事件触发器在manifest中未设置
filter
表达式。未过滤时,函数会被整个站点的所有匹配事件调用。使用manifest级别的
filter.expression
将调用限制为特定项目、问题类型或条件。
TRG-04触发器缺失ignoreSelf设置警告应用写入Jira实体并监听这些实体的事件,但manifest中未设置
filter.ignoreSelf: true
。这会导致反馈循环,应用自身的更新会触发自身的处理程序。(仅适用于Jira事件 — Confluence暂不支持)
TRG-05触发器处理函数无提前退出信息触发器处理函数在执行昂贵操作前未检查是否有实际工作要做。在函数顶部添加轻量级守卫(例如检查时间戳、检查事件字段),无需操作时提前返回。
TRG-06轮询外部服务而非使用web触发器信息调度触发器轮询外部服务获取更新。考虑替换为Forge web触发器 — 将web触发器URL注册为外部服务的webhook,仅当发生变化时才调用应用。

Performance Checks

性能检查

IDCheckSeverityWhat to Look For
PRF-01Sequential API callsWarningMultiple independent API calls made with
await
one after another instead of
Promise.all()
or
Promise.allSettled()
. Parallel duration ≈ slowest single call vs sequential duration = sum of all calls. Limit parallelism to 5–10 concurrent requests to avoid HTTP 429 rate limits — batch the rest.
PRF-02Cold start importsWarningHeavy libraries imported at the top level of resolver files. Use dynamic
import()
inside resolver handlers for rarely-used heavy dependencies.
PRF-03Missing loading statesWarningUI Kit: No
<Spinner>
or loading indicator while resolver data is being fetched. Custom UI: No loading state while
invoke()
is pending.
PRF-04Large storage entitiesWarningStoring large objects (>100KB) in a single Forge Storage key. Split into smaller chunks or use Entity Store with indexed queries.
PRF-05Blocking resolver logicWarningCPU-intensive operations (JSON parsing of large payloads, complex string manipulation, sorting large arrays) in resolvers without consideration of the 25-second timeout.
PRF-06Storage iteration instead of queryWarningFetching all items from Forge Storage via
storage.query().getMany()
and filtering in code. Use
storage.query()
with indexes,
.where()
, and
.limit()
to push filtering to the storage layer. This reduces KVS read volume and function compute time.
PRF-07No caching of stable dataWarningRepeated product API calls for data that rarely changes (e.g., custom field IDs, project metadata, workflow statuses) without any caching strategy. Cache in Forge Storage with a TTL (1 hour+ recommended). Use entity properties (free, stored by the product) for small per-entity data that doesn't need Forge Storage quota.
PRF-08Unnecessary re-rendersInfoUI Kit: State updates in loops or effects that trigger excessive re-renders. Custom UI: Missing
useMemo
/
useCallback
for expensive computations.
PRF-09Unoptimized imagesInfoCustom UI apps serving large unoptimized images or assets. Use compressed formats and lazy loading.
PRF-10Code-side filtering instead of API filteringWarningFetching all items from an API and filtering in resolver code. Push filtering to the API layer using JQL, CQL, or API query parameters so only relevant items are returned. For scheduled jobs, use date-based filters (
updated >= "lastRunDate"
) to process only recent changes. The real saving comes from fetching less data, not from batching — a single invocation iterating over thousands of unfiltered items rapidly consumes compute quota.

ID检查项严重程度检查要点
PRF-01顺序API调用警告多次独立API调用使用
await
顺序执行,而非使用
Promise.all()
Promise.allSettled()
。并行执行时长≈最慢单个调用的时长,而顺序执行时长=所有调用时长之和。将并行请求限制在5–10个以避免HTTP 429限流 — 其余请求分批处理。
PRF-02冷启动时加载重库警告resolver文件顶部导入重型库。对极少使用的重型依赖,在resolver处理函数内使用动态
import()
PRF-03缺失加载状态警告UI Kit:获取resolver数据时无
<Spinner>
或加载指示器。Custom UI:
invoke()
等待时无加载状态。
PRF-04大型存储实体警告在单个Forge Storage密钥中存储大型对象(>100KB)。拆分为更小的块或使用带索引查询的Entity Store。
PRF-05阻塞性Resolver逻辑警告resolver中执行CPU密集型操作(大payload的JSON解析、复杂字符串操作、大型数组排序),未考虑25秒超时限制。
PRF-06存储迭代而非查询警告通过
storage.query().getMany()
获取Forge Storage中的所有项目后在代码中过滤。使用带索引的
storage.query()
.where()
.limit()
将过滤推至存储层。这可减少KVS读取量和函数计算时间。
PRF-07未缓存稳定数据警告多次调用产品API获取极少变化的数据(例如自定义字段ID、项目元数据、工作流状态),未使用任何缓存策略。缓存到Forge Storage并设置TTL(推荐1小时以上)。对于小型实体数据,使用实体属性(免费,由产品存储),不占用Forge Storage配额。
PRF-08不必要的重新渲染信息UI Kit:循环或effect中的状态更新触发过多重新渲染。Custom UI:昂贵计算未使用
useMemo
/
useCallback
PRF-09未优化图片信息Custom UI应用提供大型未优化图片或资源。使用压缩格式和懒加载。
PRF-10代码端过滤而非API层过滤警告从API获取所有项目后在resolver代码中过滤。使用JQL、CQL或API查询参数将过滤推至API层,仅返回相关项目。对于调度任务,使用基于日期的过滤(
updated >= "lastRunDate"
)仅处理近期变更的项目。真正的节省来自减少数据获取量,而非批量处理 — 单个调用迭代数千条未过滤项目会快速消耗计算配额。

Output Format

输出格式

ALWAYS present findings as a single flat list sorted by severity (Critical first, then Warning, then Info). Do NOT group issues by category — interleave categories within the severity-sorted list. Use this template:
undefined
始终将审查发现整理为按严重程度排序的单一扁平列表(严重优先,其次警告,最后信息)。请勿按分类分组 — 同一严重程度内可混合不同分类的问题。使用以下模板:
undefined

Forge App Review Results

Forge应用审查结果

Summary

摘要

  • 🔴 Critical: X issues
  • 🟡 Warning: Y issues
  • 🔵 Info: Z issues
  • 🔴 严重:X个问题
  • 🟡 警告:Y个问题
  • 🔵 信息:Z个问题

Issues

问题列表

🔴 SEC-01: Overly broad scopes Location:
manifest.yml
line ~X Detail: Scope
write:jira-work
is declared but only
read:issue:jira
is used in resolvers. Fix: Replace with granular scope
read:issue:jira
. Remove unused write scope.
🟡 PRF-01: Sequential API calls Location:
src/resolvers/index.js
line ~Y Detail: Three
requestJira
calls awaited sequentially. These are independent and can run in parallel. Fix: Wrap in
Promise.all([call1, call2, call3])
.
🔵 ARC-09: Missing TypeScript Location: Project root Detail: Project uses JavaScript. TypeScript would catch type errors at build time. Fix: Consider migrating to TypeScript. Run
forge create
with TypeScript template for reference.

When no issues are found in a category, explicitly state it:
Security: No issues found ✅ Triggers & Scheduling: No issues found (or: No triggers defined)

---
🔴 SEC-01: 权限范围过宽 位置:
manifest.yml
第X行左右 详情:声明了
write:jira-work
权限,但resolver中仅使用了
read:issue:jira
权限。 修复方案:替换为细粒度权限
read:issue:jira
,移除未使用的写入权限。
🟡 PRF-01: 顺序API调用 位置:
src/resolvers/index.js
第Y行左右 详情:三个
requestJira
调用被顺序await。这些调用相互独立,可并行执行。 修复方案:使用
Promise.all([call1, call2, call3])
包裹。
🔵 ARC-09: 未使用TypeScript 位置:项目根目录 详情:项目使用JavaScript。TypeScript可在构建时捕获类型错误。 修复方案:考虑迁移至TypeScript。参考
forge create
的TypeScript模板。

若某分类未发现问题,需明确说明:
安全:未发现问题 ✅ 触发器与调度:未发现问题(或:未定义触发器)

---

Anti-Patterns — Do NOT Do These

反模式 — 请勿执行以下操作

  • Do NOT modify code unless the user explicitly asks for fixes
  • Do NOT skip reading
    manifest.yml
    — it is the foundation of the review
  • Do NOT guess about permissions — cross-reference every scope against actual API calls in code
  • Do NOT report issues without a specific file/line location when possible
  • Do NOT combine multiple issues into one finding — each gets its own entry
  • Do NOT only check one category — always review all five (Security, Architecture, Cost, Performance, Triggers & Scheduling)
  • Do NOT suggest adding dependencies to fix issues — prefer built-in solutions
  • Do NOT report issues about test files or dev tooling unless they affect production

  • 除非用户明确要求修复,否则请勿修改代码
  • 请勿跳过读取
    manifest.yml
    — 这是审查的基础
  • 请勿猜测权限 — 每个权限范围都需与代码中的实际API调用交叉验证
  • 可能的情况下,请勿报告无具体文件/行位置的问题
  • 请勿将多个问题合并为一个发现 — 每个问题单独成项
  • 请勿仅检查一个分类 — 始终审查所有五个维度(安全、架构、成本、性能、触发器与调度)
  • 请勿建议添加依赖项来修复问题 — 优先使用内置解决方案
  • 除非影响生产环境,否则请勿报告测试文件或开发工具相关问题

Edge Cases

边缘情况

Minimal App (Hello World)

极简应用(Hello World)

If the app is very simple (1 module, 1 resolver, minimal UI), still run all checks but expect mostly clean results. Report Info-level suggestions for future growth (e.g., "Consider TypeScript as the app grows").
若应用非常简单(1个模块、1个resolver、极简UI),仍需执行所有检查,但预期大部分结果无问题。报告信息级别的未来增长建议(例如:「随着应用扩展,考虑使用TypeScript」)。

Custom UI vs UI Kit

Custom UI vs UI Kit

Detect which type by checking:
  • UI Kit: imports from
    @forge/react
    , uses
    ForgeReconciler
    , JSX with Forge components
  • Custom UI: has a
    static/
    directory or
    resources
    in manifest, imports from
    @forge/bridge
  • Both: some apps use both — check each separately
通过以下方式检测类型:
  • UI Kit:导入
    @forge/react
    ,使用
    ForgeReconciler
    ,使用Forge组件的JSX
  • Custom UI:存在
    static/
    目录或manifest中的
    resources
    ,导入
    @forge/bridge
  • 混合类型:部分应用同时使用两者 — 分别检查

Monorepo / Multi-Module

单体仓库 / 多模块

If the manifest declares multiple modules, trace each module's function key to its resolver independently. Don't assume all modules share the same issues.
若manifest声明了多个模块,需独立跟踪每个模块的function密钥对应的resolver。不要假设所有模块存在相同问题。

No Manifest Found

未找到Manifest

If
manifest.yml
doesn't exist in the workspace, stop and tell the user: "No
manifest.yml
found. Are you in the correct Forge app directory?" Do NOT proceed without the manifest.
若工作区中不存在
manifest.yml
,停止审查并告知用户:「未找到
manifest.yml
。您是否在正确的Forge应用目录中?」无manifest时请勿继续审查。