Skip to content

bug(ingestion): request.get/post client calls can be misclassified as Express routes #692

@JaysonAlbert

Description

@JaysonAlbert

Summary

JS/TS ingestion currently treats request.get('/path') / request.post('/path') style HTTP client calls as Express/Hono route registrations in some cases, which creates incorrect Route / HANDLES_ROUTE data and breaks downstream HTTP contract matching.

This showed up while indexing a frontend+backend workspace where the frontend uses umi-request (via a local request wrapper) and the backend uses Spring Boot.

Why this matters

This is not just a group-sync issue.

Once the frontend call is misclassified as a provider:

  • the graph contains false-positive route providers on frontend files
  • FETCHES edges are incomplete or misleading
  • group sync has no chance to match the real consumer/provider contracts correctly
  • even analyzing frontend + backend together in one directory does not fix the problem

Minimal reproduction

Frontend utility

import { extend } from 'umi-request';

const request = extend({ credentials: 'include' });
export default request;

Frontend consumer

import request from './request';

export async function loadUsers() {
  return request.get('/api/users');
}

Backend provider

import { Router } from 'express';
const router = Router();

router.get('/api/users', (req, res) => {
  res.json([{ id: 1 }]);
});

Expected behavior

  • request.get('/api/users') should be indexed as an HTTP consumer
  • the frontend file should contribute a FETCHES edge / consumer contract
  • the frontend file should not produce a Route node or HANDLES_ROUTE provider contract

Actual behavior

The .get/.post member call shape is currently ambiguous in JS/TS ingestion:

  • app.get('/x') / router.get('/x') is a real route registration
  • request.get('/x') / apiClient.post('/x') is a client call

Today the ingestion path can classify the second category as the first one because the AST pattern only looks at the member method + first string argument, without receiver provenance.

Root cause

The relevant logic appears to be in:

  • gitnexus/src/core/ingestion/tree-sitter-queries.ts
  • gitnexus/src/core/ingestion/workers/parse-worker.ts
  • gitnexus/src/core/ingestion/pipeline.ts

In particular:

  • the express_route query captures any member_expression with .get/.post/... plus a string path
  • the worker then turns that capture into decoratorRoutes
  • later the route registry turns those into Route nodes and HANDLES_ROUTE edges

This works for app.get(...) / router.post(...), but it is too broad for imported HTTP clients like:

  • request.get(...)
  • request.post(...)
  • apiClient.get(...)
  • client.post(...)

Additional context

I locally tested this in two scenarios:

  1. separate repos + group sync
  2. frontend and backend copied into one directory and analyzed together

In both cases the root problem remained: frontend request clients were not cleanly represented as consumers, and some frontend service files were treated as route providers.

Suggested direction

A robust fix likely needs receiver-aware classification during ingestion, not just more source-scan patterns in group sync.

One reasonable approach would be:

  • keep route/provider extraction for proven route registrars (app, router, express.Router(), new Hono(), etc.)
  • classify imported HTTP clients separately (axios, axios.create(), umi-request / extend() wrappers, etc.)
  • avoid turning unknown .get/.post('/path') receivers into providers by default

That would preserve current Express/Hono support while avoiding false-positive route providers from frontend HTTP clients.

Environment

Observed on current main as of 2026-04-07 while indexing a real mixed frontend/backend workspace.

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