Skip to content

#1790 drops every identifier-rooted member call, not just built-in collections — 8 tests red on main #1794

Closed

Description

@bompus

de5adba (#1790) makes codegraph_explore, callers, impact and Steps lose the callee of any
a.b.c() call whose chain is rooted at a project identifier. Eight of this repo's own tests fail on
main because of it.

Bisect

Same worktree, same bun install, __tests__/{ui-steps-cross-tier,ui-steps-api-servers,nextjs,react-native-bridge}.test.ts:

commit result
2f8cce5 base 90 passed
7be699c #1785 90 passed
b8d46d1 #1788 90 passed
72c1ff1 #1789 90 passed
de5adba #1790 8 failed, 82 passed

de5adba re-run back-to-back after its parent in the same checkout, so this is not install state.
Node v24.16.0, Windows 11, vitest run.

Failing tests

nextjs.test.ts > nextjs: end to end > a page's Steps picture fires from its load, crosses to the server action, and draws the pages it leads to as boundaries
nextjs.test.ts > nextjs: end to end > an endpoint anchors as any server route does
react-native-bridge.test.ts > React Native cross-platform pairing — end to end > links the Android (@ReactMethod) and iOS (RCT_EXPORT_METHOD) impls of a JS-called method
ui-steps-api-servers.test.ts > in the code's order > reads an Express handler as it is written, helper and all
ui-steps-api-servers.test.ts > Express > draws the handler's database write, the queue job, the email, and the 201 — after the middleware
ui-steps-api-servers.test.ts > Express > walks an inline handler as the route itself, into the service's read and its 404
ui-steps-cross-tier.test.ts > the Steps picture across the tiers > draws the route as a boundary the form crosses to (⇢), and enters it on request
ui-steps-cross-tier.test.ts > the Steps picture across the tiers > a server action called from a client component is a crossing to the server, by its directive

Representative assertion — ui-steps-cross-tier.test.ts:461:

expect(effect(p, 'database')?.effect?.by.name).toBe('createUserAction')
AssertionError: expected undefined to be 'createUserAction'

Cause

src/extraction/tree-sitter.ts replaces tsJsChainRoot with isUnresolvedTsJsChain:

function isUnresolvedTsJsChain(node: SyntaxNode, source: string): boolean {
  let cur: SyntaxNode | null = node;
  while (cur && TS_JS_CHAIN_RECEIVER_TYPES.has(cur.type)) {
    cur = getChildByField(cur, 'object');
  }
  return !!cur && cur.type === 'identifier' && getNodeText(cur, source) !== 'window';
}

The old gate suppressed a member call only when the chain was rooted in TS_JS_HOST_GLOBAL_ROOTS
(chrome, document, Math, …). The new one suppresses every identifier-rooted chain except
window
. db.users.insert(x), queue.add(job), mailer.send(msg) and res.status(201) now emit
no call at all, so Steps effects have no by and cross-tier boundaries are not drawn.

The comment #1790 removed had recorded the opposite judgement for exactly this case:

A chain rooted at a project value (window.MyNs.run(), store.getState().act(), ref.value.m())
keeps the bare name — those targets are real, and dropping them would cost far more recall than
the mis-bind costs precision.

#1566 wants holder.values.get() unresolved because the property type is unknown. That is a subset
of what the predicate now matches: a chain is being treated as untyped whenever it is
identifier-rooted, which is the common case for project objects, not the exception.

Repro

git checkout de5adba
bun install --frozen-lockfile
node node_modules/vitest/vitest.mjs run __tests__/ui-steps-cross-tier.test.ts \
  __tests__/ui-steps-api-servers.test.ts __tests__/nextjs.test.ts \
  __tests__/react-native-bridge.test.ts

Reverting de5adba restores 90/90 on those four files, and ts-chained-receiver.test.ts plus
resolution.test.ts stay green at 296/296 with #1792 (cece072) also applied.

Not proposed as the fix — narrowing the predicate back toward the #1566 case (an inferred-collection
receiver) rather than all identifier roots looks like the direction, but I have not measured which
narrowing keeps #1566 green without costing the recall above.

Activity

  1. inth3shadows commented on Sep 9, 2026

    @inth3shadows
    Contributor

    Reproduced on Linux, and the full suite turns up one more test with this cause than the four files above cover.

    __tests__/mcp-callers-truncation.test.ts > codegraph_callers truncation > marks the cut inside each per-definition section too — not in the list above, same bisect result:

    commit mcp-callers-truncation
    72c1ff1 #1789 (de5adba's parent) passes
    3ed73bc current main fails

    How this was arrived at

    Not a targeted run — it fell out of baselining an unrelated branch. I ran the full suite (npm test, 273 files) on a merge of current main, got 13 failures, then re-ran the same 8 files on clean upstream/main to check whether my merge caused them. Identical set, so all 13 are main's. Bisecting those 13 against 72c1ff1 and 2f8cce5 splits them three ways:

    The 2f8cce5 row above is worth noting against the bisect table in the opening comment: it is listed there as a green base at 90 passed, which it is for those four files — but three other files already fail at it, from #1759. Worth separating so #1790's fix isn't judged against a suite that stays red for a different reason.

    Environment: Linux 6.6 (WSL2), vitest run, on both Node v22.23.2 (inside this repo's engines range) and v26.7.0 (outside it). The two are identical — 13 failed / 163 passed each, same 13 names, diffing byte-for-byte. The runtime is not a factor anywhere in this, the perf assertion included: 138.11 on v22 and 143.14 on v26 against its < 100 budget, so that one is a slow machine rather than a Node version.

    Flagging the platform because #1773 makes the point that the Windows counts track machine load — these are not timeouts, they are assertion failures on missing graph edges, and they reproduce off Windows on a supported Node.


    Edited to attribute the green-at-parent row for the 8 to your table rather than to a run of my own, and to add the v22 numbers — the original said only v26 and asked readers to discount the counts for it, which is no longer necessary.

    @danusha2345's correction below supersedes my third bullet: the hardReset → reset assertion is #1790's class, not #1759's, so the split is 10 / 2 / 1, not 9 / 3 / 1.

  2. danusha2345 commented on Sep 9, 2026

    @danusha2345
    Contributor

    Confirming the three-way split on Linux against 3ed73bc. With #1760 merged onto current main (head 2b126b6, no conflicts): ui-steps-api ×2 and the loginFlow → fetchUser half of object-literal-methods pass. The remaining failure in that same test is the other assertion, hardReset → reset via useStore.getState().reset() — an identifier-rooted a.b().c() chain, i.e. the #1790 class you bisected, not #1759. So #1760 clears the #1759 group and leaves the #1790 group untouched.

  3. bompus commented on Sep 11, 2026

    @bompus
    ContributorAuthor

    Thanks both — the 10 / 2 / 1 split matches what I see. #1844 is the fix for the #1790 group: it keeps the full nested receiver text so the call-site evidence Steps and the framework resolvers need survives, while generic import and last-name matching still refuse to bind an imported root or an unrelated get. It bumps the extraction version, so existing indexes need a rebuild after it lands. The hardReset → reset assertion via useStore.getState().reset() is in that PR's test set.

  4. danusha2345 commented on Sep 15, 2026

    @danusha2345
    Contributor

    Rechecked on main@58c07e8 (after #1862 and #1849): object-literal-methods (both loginFlow → fetchUser and hardReset → reset), ui-steps-api, mcp-callers-truncation and release-main-regressions all pass on Linux (WASM arm). Closed #1760 as superseded by #1862. From my side this issue looks resolved on main.

  5. colbymchenry commented on Sep 27, 2026

    @colbymchenry
    Owner

    Thanks for the detailed report, @bompus, and thanks for using CodeGraph! This is fixed on main by the regression-audit merge (f1ea898): identifier-rooted member calls keep their call evidence again, and the tests this broke are green. The fix will be in the next release.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions