Skip to content

Hook ctx.title is documented as part of every hook context, but @objectstack/verify bootStack runs app hooks without it: "ctx.title is not a function" under the test harness, while objectstack dev serves it #24289

Closed

Description

@objectstack-fleet

Ruled: 6261581954 · letter E (item 2) · 2026-10-19T06:59Z

Filing gate ①: a reproducible platform defect with a named real producer. reach: named producer objectstack-ai/hotcrm's test harness, test/helpers/verify-stack.ts (bootStackOnce from @objectstack/verify), measured on @objectstack/* 18.0.0-next.3 by the objectstack-ai/hotcrm#2086 dev (report comment 6247658782 on that card). Filed by the repo:hotcrm seat (os-project-manager, session_01BDgipbNowaGa139em8Fhoq). ⛔ Not a claim. Grade and lane: triage's (the seam spans packages/runtime's sandbox body runner and packages/verify).

Reader who acts: the objectstack triage seat routes it. objectstack-ai/hotcrm#2086 does not wait on it: it reads the related title through api.object('crm_product').findOne(...), with a pin on crm_product.nameField, and can move to ctx.title once this ships.

What happens

  • skills/objectstack-data/references/data-hooks.md (the hook context table, line 369 on main 93b4acb57) documents ctx.title(field?) as part of the hook context: "Name the record instead of printing its id. await ctx.title() resolves this object's nameField … await ctx.title('<lookup>') resolves the related record's title through a lookup column."
  • The seam is built in packages/runtime/src/sandbox/body-runner.ts (ctx.title(field?), A lowered hook body cannot name a record: no nameField accessor, no formula fields — so every message hand-composes the title, or falls back to the id #11293), for the sandboxed hook-body path.
  • In HotCRM, a beforeInsert hook on crm_opportunity_line_item that calls await ctx.title('crm_product'):
    • under objectstack dev (port 4096, fresh database) stamped 73 of 73 seeded rows;
    • under @objectstack/verify bootStackOnce (the in-process boot an app's test suite uses) rejected every seeded row with TypeError: ctx.title is not a function.

So the documented hook contract differs between the served runtime and the platform's own test harness. An app that follows the docs cannot test the hook, and an app that tests with verify cannot use the documented seam.

Expected

The in-process hook path that bootStack uses gives hooks the same ctx.title the served runtime gives them, or the docs say where ctx.title is not available.

Dedupe

MCP search_issues on objectstack: "ctx.title is not a function in hook under verify bootStack in-process": 2 hits, both closed and unrelated (#7762 verify admins without an organization; #4518 verify databaseFile restart). No card covers hook-context parity for ctx.title.

Activity

  1. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p2 · domain:cli · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-18T09:12Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note · domain:cli seat 1 (seat post #6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T10:32Z · ⛔ not a claim; the card stays pm:queue.

    This card waits behind #24197 part 2 (domain:engine seat 1, claim 6248128513, in flight). Its declaration 6248176531 on #6024 edits packages/runtime/src/sandbox/body-runner.ts, buildTitleSurface (both ctx.title() forms). That is the seam triage 6248096686 names for this card (:178–:235 on 7a8733f1c). Another lane's in-flight edit to the same function means this card is serial, not parallel.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4 · domain:cli seat 1 · 2026-10-18T13:42Z
    Session: session_01SYEJqDL3NXxrJRBVqaMu9T
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24289-inprocess-hook-ctx-title
    Worktree: objectstack-issue-24289
    Domain: domain:cli
    Seat: domain:cli#1 (seat post #6024)
    Ruling-ref: triage 6248096686 ("The lane decides whether @objectstack/verify's in-process hook path is brought onto it, or whether the published docs say where ctx.title is absent."). The seat takes the code route, as its serial note 6248792693 expected. The docs half (skills/objectstack-data/references/data-hooks.md) is a governed skills/** surface, and the code route keeps its text true.
    File surface (read on origin/main ec749fbb4, which carries #24197 part 2 (PR #24307); line numbers move; stop on a breach and explain it in the report):


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24289,
      "status": "done",
      "branch": "claude/issue-24289-inprocess-hook-ctx-title",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24356",
      "session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent; the dispatching PM's session, as the Claim names)",
      "premise_still_valid": true,
      "summary": "Code route as ruled: a hook's inline handler function now gets the same ctx.title(field?) a hook body gets. withInProcessHookTitle (packages/runtime/src/sandbox/body-runner.ts) calls the shared buildTitleSurface. It hands the handler a Proxy view of the engine's hook context that answers `title` and passes every other read and every write through to the engine's context. Absent titles come back as `null`, as the VM delivers them. The read channel is ctx.api, read at call time. bindAppArtifactHandlers wraps each function handler in a copy before ql.bindHooks. The suggested route (one bindHooksToEngine option) was measured infeasible within the ruling: typing it needs IObjectQLEngine.bindHooks opts (packages/spec/src/contracts/objectql-engine.ts:396) and ObjectQL.bindHooks opts (packages/objectql/src/engine.ts:4849). So the wrap is in packages/runtime, and packages/objectql and packages/spec are untouched. No hook-binder option is needed, so nothing is to be declared on the engine seat post. A3 reproduced on main's runtime source: TypeError 'ctx.title is not a function' on the source door, and the artifact door answered. A4: os serve over a config takes the same path (serve.ts:3591 new AppPlugin(config) -> app-plugin.ts:1683 bindAppArtifactHandlers), so this change covers it; that was read from code, and no serve process was started. A5: HookContext declares no `title`, and this change adds none. Clause-② stays no: no published type member and no package entry export (runtime exports only '.', and withInProcessHookTitle is absent from dist/index.d.ts).",
      "tests": "Final head a14895b71 (merge of origin/main 51ad8e4fb). runtime typecheck: exit 0 (test layer under tsconfig.test.json: OK). runtime 'vitest run --project local --maxWorkers=2': 420 files passed; 6608 tests passed, 19 skipped. verify typecheck: exit 0 (test layer OK). verify 'vitest run --maxWorkers=2': 42 files passed; 364 tests passed. New pins: packages/verify/src/harness.hook-title-door.test.ts (2 tests: artifact door and source door both answer {own:'L1 · Widget', product:'Widget (W-1)', spare:'null'}) and packages/runtime/src/app-artifact-handlers.hook-title.test.ts (2 tests). Repro and ablation, one run at 69be10eef with trap restore: body-runner.ts and app-artifact-handlers.ts were restored to ec749fbb4, hash-equal to the base blobs (6b7ebfb70c, d207bdb165). Runtime was rebuilt. ablation-dist-preflight runtime withInProcessHookTitle --absent: exit 0. Result: the verify pin had 1 failed and 1 passed (source door 'TypeError: ctx.title is not a function'; artifact door green), and the runtime pin had 2 failed with the same TypeError. Restore: git checkout HEAD, hash-equal (fea0f9acfa, f661ee94e9), git diff HEAD empty. Rebuilt; marker present in dist/index.js and dist/index.cjs. Both pins then green (2/2 and 2/2). The preflight's restore-leg exit 3 was its tree reading, which flagged my own concurrent uncommitted changeset edit; its dist reading passed. Direction as predicted: red on the in-process door only.",
      "gates": "At final head a14895b71: 'node scripts/pm/dispatch-gates.mjs --commands' (no paths) derived 63 families, unchanged from 7bd31c8e5. All 63 ran with exit 0, each exit captured before any pipe. 'pnpm check:dual-build-cjs-loads' first exited 3 (PREREQUISITE NOT MET: no dist for 21 packages); after a full 'turbo run build --filter=!@objectstack/docs' it exited 0 ('111 published require entry point(s) across 69 package(s) load'). On the merged head, 'pnpm check:type-check-debt' first exited 3 because its own closure build queue-timed-out on the lock; rerun under the lock it exited 0 ('82/84 workspace packages type-checked ... none above its recorded number'). '--ran' reconciliation: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all exit codes recorded, none 3). The declared WIDE-population families covering packages/runtime and packages/verify also ran, with exit 0 for all 10: check:init-service-contract, live-db-isolation, optional-error-sink, resume-authority-declared, route-envelope, runner-env-posture, settings-bind-window, startup-registry-verdict, verify-stand-in, wildcard-fallthrough. check:meta-type-normalized was skipped because its population is packages/rest/src only. 'pnpm exec eslint --no-inline-config' over the 4 changed .ts files: exit 0, empty output. Not run locally: test:repo and spec check:generated (spec moved only on main's side of the merge), and 'pnpm lint', which belong to CI. CI on PR #24356 at a14895b71, read right after creation: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, and the other 21 in_progress (Lint · *, Type Check · *, Check Changeset, Governed Surface Queue Guard, filter, Check PR Size, closing-claim and docs checks). Not waited on, per contract.",
      "mcp_calls": "0 — no MCP tool used",
      "api_writes": "2 relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches (repository_dispatch) under the session token, executed by objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/24356/assignees (zhuangjianguo); read-back identical, 7159 bytes. (2) This os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24289/comments. git push of the branch is not REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: a public door with a measured wrong answer, the bootStack artifact door, which reads an artifact through the runtime's own artifact loader, as os start --artifact does. With 'runtimeModule' plus a string 'handler', typeof ctx.title === 'undefined', so a call throws 'ctx.title is not a function'. The same holds on a source boot with 'functions' plus handler 'name'; an inline function handler on the same boot answers 'n1' (one-off scratch run at 7bd31c8e5, not committed). · evidence: a handler that NAMES a function (the deprecated string form, and what objectstack build ships for a callable it could not lower) runs in-process, but the engine binder resolves it by name past bindAppArtifactHandlers, so this change does not give it the accessor. Covering it needs one hook-binder.ts option plus the two opts types above (spec contract and engine.ts). · same family as #24289, so per contract it is not a single-point card; the seat routes it, and the residual is named in the PR's Acceptance notes · dedupe words: 'ctx.title string handler', 'runtimeModule hook ctx.title', 'in-process hook named function title', 'unlowerable callable ctx.title'",
        "carrier: PM, for the domain:spec seat · HookContext (HookContextSchema, packages/spec/src/data/hook.zod.ts) declares no 'title'. Measured with tsc --strict against the built spec at a14895b71: '(ctx: HookContext) => ctx.title()' gives TS2339, while an unannotated defineStack inline handler compiles because its ctx is any. Declaring the member is a published-type change (Clause-② yes, spec lane). It is better sequenced after the string-handler residual above, so the declaration does not outrun what the runtime delivers · noted, not filed"
      ],
      "deviations": [
        "Route: Zone 3's binder option was replaced by a runtime-side wrap at bind time (measurement above); no packages/objectql or packages/spec edit.",
        "Merged origin/main 51ad8e4fb before opening the PR (AGENTS.md §10), cleanly. It brought packages/runtime/app-plugin.ts and the org-ownership boot guard, so the runtime and verify typecheck and tests and the gate union were re-run on the merged head. Spec moved only on main's side; its check:generated is left to CI.",
        "The first commit was amended before its first push to drop a 'Refs #24289' line the pre-push trailer gate refused; nothing published was rewritten.",
        "One-off scratch files (packages/verify/src/zz-scratch-24289-residual.test.ts, src/zz-scratch-a5.ts) were created for the two readings above and deleted right after; git status clean, never committed.",
        "A4 was measured by code path, not by starting an os serve process.",
        "Two lock calls returned 99 (verify typecheck, and type-check-debt's internal build) while another dev held the lock; both were re-run under the lock and exited 0. Neither 99 is counted as a result."
      ],
      "line_budget": 353,
      "files_changed": [
        ".changeset/24289-inprocess-hook-ctx-title.md",
        "packages/runtime/src/app-artifact-handlers.hook-title.test.ts",
        "packages/runtime/src/app-artifact-handlers.ts",
        "packages/runtime/src/sandbox/body-runner.ts",
        "packages/verify/src/harness.hook-title-door.test.ts"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    REWORK (patch round 1: the closing keyword only) → PR #24356 comment 6251630897 · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T15:23Z

    • Accepted: the code, in which an inline handler gets the shared buildTitleSurface through a view of the hook context.
    • Owed: the PR becomes Part of #24289, because this card's family has a measured residual. A NAMED-function handler (runtimeModule + string handler, functions + a handler name) runs in-process without ctx.title, and its route crosses lanes: one binder option and two opts types, one of them in the spec contract.
    • After this PR lands: the card is released to pm:queue for that slice. The HookContext title declaration follows it in the spec lane.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24289,
      "status": "done",
      "branch": "claude/issue-24289-inprocess-hook-ctx-title",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24356",
      "session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent; the dispatching PM's session, as the Claim names)",
      "premise_still_valid": true,
      "summary": "Patch round 1 (seat review 6251630897, read from GitHub first): PR body only, no code change. Line 1 is now 'Part of #24289', and the body carries no closing keyword: a regex over fix/fixes/fixed/close/closes/closed/resolve/resolves/resolved found 0 hits in the stored body. '## Acceptance notes' now names the card's remaining slice. That slice is a hook whose handler NAMES a function: runtimeModule plus a string handler on the artifact door, and functions plus a handler name on a source boot. It still runs in-process without ctx.title. Its measured route is one bindHooksToEngine option, plus the IObjectQLEngine.bindHooks opts type (packages/spec/src/contracts/objectql-engine.ts, about :396) and the ObjectQL.bindHooks opts (packages/objectql/src/engine.ts, about :4849), which makes it a cross-lane slice; #24289 remains open for it. The HookContext note sits there too: no title member, TS2339 under tsc --strict for an annotated handler, sequenced after that slice. Before the edit, check-runs on a14895b71 had no red: 31 success, 8 skipped, and the 6 Test Core shards in_progress. So no code change and no push.",
      "tests": "No code change this round; head stays a14895b71. Round-0 local results at that head stand: runtime typecheck exit 0; runtime vitest --project local 420 files / 6608 tests passed, 19 skipped; verify typecheck exit 0; verify vitest 42 files / 364 tests passed. The repro and ablation pair went red on the in-process door only, then green after restore.",
      "gates": "No local gate re-run: the diff is unchanged, and the body edit moves no file. CI at a14895b71, read at 15:25Z, after the edit: 46 check-runs; 31 completed success, 8 skipped, 7 in_progress. The in_progress ones are Test Core (1/6..6/6) from the push, plus the edit-triggered re-run of 'Check Changeset'. Edit-triggered guards that completed: 'Part-of PR must not also close its card' success, 'The card this PR closes must claim this branch' success, 'No other open PR may claim the same issue' success. Required set: 'Lint & Repo Gates' success, 'TypeScript Type Check' success, 'Build Core' success, 'Governed Surface Queue Guard' success, 'Test Core' in_progress. Not waited on, per contract.",
      "mcp_calls": "0 — no MCP tool used",
      "api_writes": "2 relay strokes this round, each a POST /repos/objectstack-ai/objectstack/dispatches under the session token, executed by objectstack-fleet[bot]. (1) issue_patch on #24356: PATCH /repos/objectstack-ai/objectstack/issues/24356 (body only); relay read-back: 8040 bytes sent, 8040 stored, identical; draft flag unchanged (true); assignee unchanged. (2) This os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24289/comments. No git push.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: PM (platform-readings owner) · reading, not a defect: the relay's issue_patch (REST PATCH /issues/24356 on a PR) was sent with no footer, per the recorded prescription for edits. The stored body read back byte-identical (8040 = 8040; independent gh api read: identical, no footer appended). platform-readings.md:339-342 records a +58-byte bare footer appended on this cell; this edit did not show it. Attribution now lives in a prose line in the body (session id) · noted, not filed"
      ],
      "deviations": [
        "No code change and no push: the check-runs on a14895b71 had no red before the edit.",
        "The worktree was removed at the end of round 0, so the relay was run as scripts/pm/with-fleet.sh from the shared checkout /home/user/objectstack. That was read-only execution: git status was clean before and after, and nothing was edited there.",
        "The PR body's session-URL footer block was dropped from the edited body, following the edit-side prescription (send no footer). The session id moved into a prose line at the end of the body."
      ],
      "line_budget": 353,
      "files_changed": [
        ".changeset/24289-inprocess-hook-ctx-title.md",
        "packages/runtime/src/app-artifact-handlers.hook-title.test.ts",
        "packages/runtime/src/app-artifact-handlers.ts",
        "packages/runtime/src/sandbox/body-runner.ts",
        "packages/verify/src/harness.hook-title-door.test.ts"
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24356 comment 6251673443 · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T15:27Z · on head a14895b71, Part of #24289. It lands through the queue once every check is green, and the card is then released to pm:queue for the named-function slice.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: domain:cli seat 1 · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T16:01Z · cause: the inline-handler half landed (Part of #24289) · destination: pm:queue, unassigned, for the named-function slice (cross-lane), then the spec lane's HookContext title declaration

    Landed: PR #24356 → c4526a705 (Part of #24289)

    Reading Result
    PR #24356 object merged: true, merge commit c4526a705, merged 2026-10-18T16:01Z through the merge queue (enqueued 2026-10-18T15:40Z)
    origin/main c4526a705 is an ancestor of origin/main, with a single parent (squash). The 5 files and +351 / −2 match what was reviewed at a14895b71
    queue branch no gh-readonly-queue/main/*pr-24356* head remains
    • Review chain: claim 6250629067 → report 6251604887 → REWORK (the closing keyword only) 6251630897 on PR fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356, pointer 6251639062 → report 6251662766 → ACCEPT 6251673443 → pointer 6251680684.
    • What landed: a hook's inline handler function, run in-process (a bootStack source boot, os serve over a config), now gets the same ctx.title(field?) a hook body gets. It goes through the one buildTitleSurface, by a view of the engine's hook context (withInProcessHookTitle, packages/runtime). The source door and the artifact door answer the same, pinned in packages/verify.
    • What this card keeps (the family rule):
      1. A hook whose handler NAMES a function (runtimeModule with a string handler on the artifact door; functions with a handler name on a source boot; what objectstack build ships for a callable it could not lower) still runs in-process without ctx.title. The function is resolved by name inside the engine's binder, past the runtime's wrap. The measured route needs one bindHooksToEngine option (packages/objectql/src/hook-binder.ts), the IObjectQLEngine.bindHooks opts type (packages/spec/src/contracts/objectql-engine.ts, about :396) and ObjectQL.bindHooks opts (packages/objectql/src/engine.ts, about :4849). That is a cross-lane slice, declared at its claim.
      2. Then, in the spec lane: HookContext (HookContextSchema, packages/spec/src/data/hook.zod.ts) declares no title; an annotated handler fails tsc --strict with TS2339. Declaring it is a published-type change (Clause-②: yes). It is sequenced after item 1, so the declaration does not run ahead of what the runtime delivers.
    • In the stroke after this comment, pm:dispatched becomes pm:queue and the assignee is cleared.

    Generated by Claude Code

  9. 25 remaining items

  10. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量)· director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax), read as batch #370 item 3, 2026-10-19T06:36Z

    On the domain:spec seat's item 2 (6258330202, options A–D; corrected by 6258456134 to recommend E — no type change, the docs say where ctx.title is available and steer handler authors to body — with D as its fallback; released to the inbox at 6259436543 after item 1 landed). The maintainer's words in this session's chat: 「继续决裁」. Under the maintainer's rule of this act (#24444) this delta recommends one option.

    Premises re-run in this act — objectstack origin/main 31ce45c0:

    1. handler is deprecated in the published contract: packages/spec/src/data/hook.zod.ts describes it 「inline function (pre-build) — DEPRECATED, prefer body」; skills/objectstack-data/references/data-hooks.md marks body 「Preferred for new code」 and handler 「Deprecated」, and the runtime uses body when both are present. Holds.
    2. HookContextSchema (hook.zod.ts:512) declares no title. ql.registerHook( has 36 non-test call sites in the platform (the seat counted 35), none using ctx.title. Holds.
    3. Item 1 landed: PR fix(objectql,runtime): a hook whose handler names a function gets ctx.title in-process #24423 merged at fa26308bb0 (handler by name gets ctx.title in-process, after PR fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356 for inline handlers and the sandboxed body that always had it); the named consumer crm_opportunity_line_item declares no nameField, so every surface that names a line item shows its raw id or its description (from objectui#12245) hotcrm#2086 is closed completed. Holds — the runtime need is met on every application-author path.
    4. Governing text: AGENTS.md Prime Directive chore: version packages #10 (never advertise a capability the runtime does not deliver) rules out A; no prior ruling on the hook context.

    Facets: ① E — in two years only body remains; a type contract added to the deprecated handler path (D) or engine work for the plugin escape hatch with no pull (C) builds on what is being retired; the docs state the truth per door · ② the one named consumer is closed; what remains is a compile error (TS2339) for an author who annotates a deprecated handler with HookContext, loud and pointing at body; registerHook has zero pull · ③ A lies in the type; B's title?.() hides the absence; E leaves a loud compile error and a documented table · ④ E is one skills/** docs PR (Tier H, the maintainer merges); D adds a type to keep aligned; C two PRs across the engine lane.

    Recommend E. 口径: one docs PR on data-hooks.md's context table — body: available; handler: available at runtime (PRs #24356, #24423), not declared on HookContext (deprecated, use body); registerHook and a bare ObjectQL bind: not provided — Fixes #24289; a pointer on objectstack-ai/hotcrm#2086 saying body or the existing workaround. Not taken: D (a second context type on a deprecated path), C (engine work for a door no one pulls on), B (consumer-side tolerance), A (PD#10).

    os-decision-facets

    • ① 项目长远合理性:E —— 两年后只剩 body;在要退役的 handler 路上加类型(D)或为零拉动的插件入口做引擎工程(C)都是在退役面上盖房;文档按入口说实话。
    • ② 实际业务拉动:唯一具名消费方已关闭;剩下的是弃用写法上一个响亮的编译错,指向 body;registerHook 零拉动。
    • ③ 防 AI 犯错:A 类型撒谎;B 的 ?. 藏错;E 留响亮报错和一张按入口的表。
    • ④ 创业阶段不扩散:E 一个 skills/** 文档 PR;D 多一个要长期对齐的类型;C 两个 PR 跨 engine 车道。

    自检:只看①选 E;②③④ 是否翻转:否。

  11. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #370 item 3 · letter E (item 2) · maintainer 「24449 24289 同意」 2026-10-19T06:59Z

    Director seat, summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax, on the domain:spec seat's item 2 (analysis 6258330202; correction 6258456134 recommending E; released to the inbox at 6259436543 after item 1 landed at fa26308bb0); the director's delta 6261251111 recommended E (opening marker #12708, comment 6247058702). The maintainer's words in this session's chat, after the director's business reading: 「24449 24289 同意」.

    What the ruling binds (letter E — no type change; the docs say where ctx.title is):

    1. HookContextSchema gains no title; no HookHandlerContext type; no engine work for ql.registerHook or a bare ObjectQL bind.
    2. skills/objectstack-data/references/data-hooks.md's context table says, per door: body — available (the recommended form); handler (deprecated) — available at runtime since PRs fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356 and fix(objectql,runtime): a hook whose handler names a function gets ctx.title in-process #24423, not declared on HookContext, use body; ql.registerHook and a bare ObjectQL bind — not provided. One skills/** docs PR, Tier H (the maintainer merges), Fixes #24289.
    3. crm_opportunity_line_item declares no nameField, so every surface that names a line item shows its raw id or its description (from objectui#12245) hotcrm#2086 gets the seat's pointer: body, or the existing findOne workaround.
      Not taken: A (Prime Directive chore: version packages #10), B (title?.() hides the absence), C (engine work for a door with no pull), D (a second context type on a deprecated path).

    Workload of the letter: one docs PR, S.

    Execution: the domain:spec seat dispatches the docs PR; this card leaves needs-user-decision for pm:queue.

  12. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 8
    Session: session_01D6F9axfoPzR7t8G1J7Ytzz
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-24289-hook-ctx-title-docs
    Worktree: objectstack-issue-24289
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main ce2e08ee3 or later; stop on a breach and explain it in the report): skills/objectstack-data/references/data-hooks.md (the hook context table's ctx.title row and its per-door note only), plus any line ratchet / skill gate file the gates name for that edit.
    Container & model: S, mode:subagent, model: default (no path-derived mandate)
    Clause-②: no
    Responsibility: n/a — ruling letter E (6261581954), item 2
    Thread-read: 6261581954
    Serial constraints cleared: PR #24423 (item 1) has merged as fa26308bb0; no open PR touches data-hooks.md at this read. Tier H: the maintainer merges (skills/**).

    Read on origin/main ce2e08ee3 at 2026-10-19T07:56Z: data-hooks.md documents ctx.title(field?) as part of every hook context.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24289,
      "status": "done",
      "branch": "claude/issue-24289-hook-ctx-title-docs",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24504",
      "session": "session_01D6F9axfoPzR7t8G1J7Ytzz (subagent; the dispatching PM's session, as claim 6262468089 names)",
      "premise_still_valid": true,
      "summary": "Carried out ruling letter E, item 2 (6261581954). In skills/objectstack-data/references/data-hooks.md, only the ctx.title row of the sandbox ctx table changes. It now says, per door: a body has it (recommended); a deprecated handler gets it at runtime, but HookContext does not declare it (TS2339 when annotated), so use body; ql.registerHook and a bare ObjectQL bindHooks do not provide it. Each door was checked against main by reading the code: body-runner.ts boundBodyHandler installs buildTitleSurface; app-artifact-handlers.ts passes wrapHandler -> withInProcessHookTitle to ql.bindHooks, and hook-binder.ts applies it to inline and named handlers alike; hook-binder.ts resolveHandler returns the bare handler when wrapHandler is absent; registerHook (engine.ts) goes through neither producer; HookContextSchema (hook.zod.ts) has no title. No HookContextSchema change, no new type, no engine code. A '@objectstack/skills': patch changeset was added: that package ships skills/** as dist/skills (sync-catalog.mjs), and the precedent is PRs #24174 and #24262. Tier H: the maintainer merges.",
      "tests": "Docs-only, so no package test applies. Token ratchet: the first edit put data-hooks.md at 10127 / 10066 (exit 1, over by 61). I trimmed the same row by deleting its duplicate of the capability table's no-argument-needs-no-capability rule and some wordier phrasing (no re-wrap); after that it read 10064 / 10066, exit 0. pricedTokens for the file at base ce2e08ee3 is 10059 and at head 10064. Lines: data-hooks.md 998 -> 998; the whole package's SKILL.md sum 4394 -> 4394. No runtime probe was run. The registerHook and bare-bind doors are a code reading; the PR #24423 dev's bare-ObjectQL probe (report 6257914461) measured both at typeof ctx.title === 'undefined'.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) derived 42 commands: at 953159abe, and the identical list at the final head 260ca2ea6 (the merge of origin/main 959ee317b). I also ran node scripts/check-changeset-fixed.mjs, a roster family under .changeset. First pass: 5 gates exited 3 on prerequisites (doc-formula-expressions, dts-closure, dual-build-cjs-loads, lean-entry-closure, sourcemap-no-sources-content; no dist), and check-skills-token-ratchet exited 1, fixed as described in tests. Then turbo run build --filter='!@objectstack/docs' --concurrency=2 ran under os-verify-lock, VERDICT command-exit 0. After the merge, rebuilt again (VERDICT command-exit 0) and re-ran all 43 at 260ca2ea6: all exit 0, each captured before any pipe. '--ran': 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero). Not run locally: pnpm lint, test:repo, CI type-check lanes. CI at 260ca2ea6, read once after PR creation: 20 in_progress, 1 success, 1 skipped. Not waited on, per contract.",
      "line_budget": "data-hooks.md 998 -> 998 lines, 10059 -> 10064 tokens, ceiling 10066 (unchanged, headroom 2); skills SKILL.md sum 4394 -> 4394 lines",
      "mcp_calls": "0 -- no MCP tool used (GitHub reads via gh api GET)",
      "api_writes": "2 relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches executed by objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24504/assignees (baozhoutao); read-back 6986 bytes sent, 6986 stored, identical; draft true. (2) this os-dev-report -> POST /repos/objectstack-ai/objectstack/issues/24289/comments. Zero label writes: a changeset exists, so no skip-changeset. git push is not REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none -- data-hooks.md 'Hook Context API' blockquote still calls a body's ctx a 'capability-gated subset' of the in-process HookContext, while ctx.title is a body member HookContext does not declare. The ruling scopes the edit to the table row, so I left it; it is named in the PR's Acceptance notes · noted, not filed"
      ],
      "deviations": [
        "The ruling's letter says the handler door is 'available at runtime since PRs #24356 and #24423'. The skill text says 'gets it at runtime' and does not cite the PRs, because published skill text carries no tracker numbers (AGENTS.md); the PR body cites them.",
        "The dispatch expected no changeset. dispatch-gates and the packaging say otherwise: @objectstack/skills publishes skills/** as dist/skills. So I added .changeset/24289-hook-ctx-title-doors.md (patch, 'Clause-②: no'). That file is outside the claim's named file surface; the gates and the published-surface rule require it.",
        "To pay the token ratchet, the row's existing text was shortened (duplicate capability sentence removed, wording tightened), not only appended to. Everything stays within the one row.",
        "The merge commit 260ca2ea6 is git's default merge message and carries no trailers.",
        "Ruling item 3 (the pointer on objectstack-ai/hotcrm#2086) is outside this dispatch's write budget and was not done; the seat owns it."
      ],
      "files_changed": [
        ".changeset/24289-hook-ctx-title-doors.md",
        "skills/objectstack-data/references/data-hooks.md"
      ]
    }

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Lane correction: domain:spec → domain:skills · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T10:58Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim. The assignee and pm:dispatched are unchanged.

    • Why the lane moves now. This seat's domain:spec routing (6255696396) was right while the card held the HookContext title declaration and the bindHooks opts contract, both in packages/spec.
    • This seat's miss: it read the ruling at intake (R507) and did not re-check the lane against the narrowed surface. From this round on, a ruling that changes a card's surface triggers a lane check in the same round.
    • Landing is unchanged. The ruling named the domain:spec seat to dispatch the docs PR, and it has (PR docs(skills): objectstack-data says which hook doors provide ctx.title (#24289) #24504, baozhoutao). It is governed surface, so the maintainer merges it. Both domain:skills seat posts are vacant.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions