GET /api/v1/packages and /meta/package/<showcase> answer 500 "Converting circular structure to JSON" — the registry stores the live defineStack manifest, plugin instances included #14309

Description

@hotlong

What happens

On a stock showcase boot (main @ a39b02a, objectui pin 67dadd602a3a, objectstack dev --seed-admin), signed in as the seeded admin:

GET /api/v1/packages -> 500 INTERNAL_ERROR
GET /api/v1/packages/com.example.showcase -> 500 INTERNAL_ERROR
GET /api/v1/meta/package/com.example.showcase -> 500 {"error":"Internal server error"}
GET /api/v1/meta/package/com.objectstack.setup -> 200

Error body of the first two:

Converting circular structure to JSON
--> starting at object with constructor '_ObjectQL'
| property 'actionActivation' -> object with constructor 'ActionActivationProjection'
| property 'store' -> object with constructor 'ObjectStoreActionActivationStore'
--- property 'engine' closes the circle

Studio calls GET /api/v1/packages three times on every open and gets 500 each time (package switcher / publish surfaces read it). The 500 is not logged server-side (filed separately).

Why

  • ObjectQL.registerApp(manifest) hands the app's defineStack(...) object to SchemaRegistry.installPackage(manifest) (packages/objectql/src/engine.ts ~4795), and installPackage stores that object verbatim as pkg.manifest (packages/objectql/src/registry.ts ~3575-3620).
  • The showcase manifest carries live runtime plugin instances (plugins: [new ConnectorRestPlugin(), new ConnectorMcpPlugin(), MarketplaceProxyPlugin, RuntimeConfigPlugin, ...], examples/app-showcase/objectstack.config.ts ~134-162). After init those instances hold the engine.
  • Since feat(actions): durable packaged-action disable — same activation ledger, dispatch-time consult #12348 (2026-08-25) the engine carries actionActivation -> store -> engine, a reference cycle, so JSON.stringify of anything that reaches the engine throws. Before that PR the same response would have serialized the entire engine graph into the payload instead of failing, which is why this only surfaces now.
  • GET /packages spreads every registry item into the response as-is (packages/rest/src/package-routes.ts ~662-700, { ...item, source: 'registry' }), so one unserializable package item fails the whole list. The Setup package has no plugin instances, hence 200.

Suggested fix

  1. Store a serializable manifest projection at install time: installPackage should keep the spec-shaped manifest (id, name, version, namespace, type, scope, description, dependencies, declared metadata) and drop runtime-only members such as plugin instances. The kernel keeps the live object; the registry item is a record, not the runtime.
  2. GET /packages / GET /packages/:id should project explicit fields rather than spreading the item, so a future non-serializable member degrades to a missing field instead of a 500.
  3. Pin it: a test that registers a package whose manifest carries an object with a reference cycle and asserts JSON.stringify(registry.getPackage(id)) succeeds, plus a route test on GET /packages over the showcase-shaped stack.

Repro

pnpm dev -- --fresh -p 3911 # showcase
curl -s -c c.txt -H 'Content-Type: application/json' -d '{"email":"admin@objectos.ai","password":"admin123"}' http://localhost:3911/api/v1/auth/sign-in/email
curl -s -b c.txt http://localhost:3911/api/v1/packages | head -c 400

Found during the objectui pin-bump dogfood (PR #14295); not caused by that bump.

Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    GET /api/v1/packages and /meta/package/<showcase> answer 500 "Converting circular structure to JSON" — the registry stores the live defineStack manifest, plugin instances included #14309

    Description

    @hotlong

    What happens

    On a stock showcase boot (main @ a39b02a, objectui pin 67dadd602a3a, objectstack dev --seed-admin), signed in as the seeded admin:

    GET /api/v1/packages -> 500 INTERNAL_ERROR
    GET /api/v1/packages/com.example.showcase -> 500 INTERNAL_ERROR
    GET /api/v1/meta/package/com.example.showcase -> 500 {"error":"Internal server error"}
    GET /api/v1/meta/package/com.objectstack.setup -> 200
    

    Error body of the first two:

    Converting circular structure to JSON
    --> starting at object with constructor '_ObjectQL'
    | property 'actionActivation' -> object with constructor 'ActionActivationProjection'
    | property 'store' -> object with constructor 'ObjectStoreActionActivationStore'
    --- property 'engine' closes the circle
    

    Studio calls GET /api/v1/packages three times on every open and gets 500 each time (package switcher / publish surfaces read it). The 500 is not logged server-side (filed separately).

    Why

    • ObjectQL.registerApp(manifest) hands the app's defineStack(...) object to SchemaRegistry.installPackage(manifest) (packages/objectql/src/engine.ts ~4795), and installPackage stores that object verbatim as pkg.manifest (packages/objectql/src/registry.ts ~3575-3620).
    • The showcase manifest carries live runtime plugin instances (plugins: [new ConnectorRestPlugin(), new ConnectorMcpPlugin(), MarketplaceProxyPlugin, RuntimeConfigPlugin, ...], examples/app-showcase/objectstack.config.ts ~134-162). After init those instances hold the engine.
    • Since feat(actions): durable packaged-action disable — same activation ledger, dispatch-time consult #12348 (2026-08-25) the engine carries actionActivation -> store -> engine, a reference cycle, so JSON.stringify of anything that reaches the engine throws. Before that PR the same response would have serialized the entire engine graph into the payload instead of failing, which is why this only surfaces now.
    • GET /packages spreads every registry item into the response as-is (packages/rest/src/package-routes.ts ~662-700, { ...item, source: 'registry' }), so one unserializable package item fails the whole list. The Setup package has no plugin instances, hence 200.

    Suggested fix

    1. Store a serializable manifest projection at install time: installPackage should keep the spec-shaped manifest (id, name, version, namespace, type, scope, description, dependencies, declared metadata) and drop runtime-only members such as plugin instances. The kernel keeps the live object; the registry item is a record, not the runtime.
    2. GET /packages / GET /packages/:id should project explicit fields rather than spreading the item, so a future non-serializable member degrades to a missing field instead of a 500.
    3. Pin it: a test that registers a package whose manifest carries an object with a reference cycle and asserts JSON.stringify(registry.getPackage(id)) succeeds, plus a route test on GET /packages over the showcase-shaped stack.

    Repro

    pnpm dev -- --fresh -p 3911 # showcase
    curl -s -c c.txt -H 'Content-Type: application/json' -d '{"email":"admin@objectos.ai","password":"admin123"}' http://localhost:3911/api/v1/auth/sign-in/email
    curl -s -b c.txt http://localhost:3911/api/v1/packages | head -c 400

    Found during the objectui pin-bump dogfood (PR #14295); not caused by that bump.

    Generated by Claude Code

    Activity

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

    Metadata

    Metadata

    Assignees

    Labels

    bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      GET /api/v1/packages and /meta/package/<showcase> answer 500 "Converting circular structure to JSON" — the registry stores the live defineStack manifest, plugin instances included #14309

      Description

      @hotlong

      What happens

      On a stock showcase boot (main @ a39b02a, objectui pin 67dadd602a3a, objectstack dev --seed-admin), signed in as the seeded admin:

      GET /api/v1/packages -> 500 INTERNAL_ERROR
      GET /api/v1/packages/com.example.showcase -> 500 INTERNAL_ERROR
      GET /api/v1/meta/package/com.example.showcase -> 500 {"error":"Internal server error"}
      GET /api/v1/meta/package/com.objectstack.setup -> 200
      

      Error body of the first two:

      Converting circular structure to JSON
      --> starting at object with constructor '_ObjectQL'
      | property 'actionActivation' -> object with constructor 'ActionActivationProjection'
      | property 'store' -> object with constructor 'ObjectStoreActionActivationStore'
      --- property 'engine' closes the circle
      

      Studio calls GET /api/v1/packages three times on every open and gets 500 each time (package switcher / publish surfaces read it). The 500 is not logged server-side (filed separately).

      Why

      • ObjectQL.registerApp(manifest) hands the app's defineStack(...) object to SchemaRegistry.installPackage(manifest) (packages/objectql/src/engine.ts ~4795), and installPackage stores that object verbatim as pkg.manifest (packages/objectql/src/registry.ts ~3575-3620).
      • The showcase manifest carries live runtime plugin instances (plugins: [new ConnectorRestPlugin(), new ConnectorMcpPlugin(), MarketplaceProxyPlugin, RuntimeConfigPlugin, ...], examples/app-showcase/objectstack.config.ts ~134-162). After init those instances hold the engine.
      • Since feat(actions): durable packaged-action disable — same activation ledger, dispatch-time consult #12348 (2026-08-25) the engine carries actionActivation -> store -> engine, a reference cycle, so JSON.stringify of anything that reaches the engine throws. Before that PR the same response would have serialized the entire engine graph into the payload instead of failing, which is why this only surfaces now.
      • GET /packages spreads every registry item into the response as-is (packages/rest/src/package-routes.ts ~662-700, { ...item, source: 'registry' }), so one unserializable package item fails the whole list. The Setup package has no plugin instances, hence 200.

      Suggested fix

      1. Store a serializable manifest projection at install time: installPackage should keep the spec-shaped manifest (id, name, version, namespace, type, scope, description, dependencies, declared metadata) and drop runtime-only members such as plugin instances. The kernel keeps the live object; the registry item is a record, not the runtime.
      2. GET /packages / GET /packages/:id should project explicit fields rather than spreading the item, so a future non-serializable member degrades to a missing field instead of a 500.
      3. Pin it: a test that registers a package whose manifest carries an object with a reference cycle and asserts JSON.stringify(registry.getPackage(id)) succeeds, plus a route test on GET /packages over the showcase-shaped stack.

      Repro

      pnpm dev -- --fresh -p 3911 # showcase
      curl -s -c c.txt -H 'Content-Type: application/json' -d '{"email":"admin@objectos.ai","password":"admin123"}' http://localhost:3911/api/v1/auth/sign-in/email
      curl -s -b c.txt http://localhost:3911/api/v1/packages | head -c 400

      Found during the objectui pin-bump dogfood (PR #14295); not caused by that bump.

      Generated by Claude Code

      Activity

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

      Metadata

      Metadata

      Assignees

      Labels

      bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

      Type

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        GET /api/v1/packages and /meta/package/<showcase> answer 500 "Converting circular structure to JSON" — the registry stores the live defineStack manifest, plugin instances included #14309

        Description

        @hotlong

        What happens

        On a stock showcase boot (main @ a39b02a, objectui pin 67dadd602a3a, objectstack dev --seed-admin), signed in as the seeded admin:

        GET /api/v1/packages -> 500 INTERNAL_ERROR
        GET /api/v1/packages/com.example.showcase -> 500 INTERNAL_ERROR
        GET /api/v1/meta/package/com.example.showcase -> 500 {"error":"Internal server error"}
        GET /api/v1/meta/package/com.objectstack.setup -> 200
        

        Error body of the first two:

        Converting circular structure to JSON
        --> starting at object with constructor '_ObjectQL'
        | property 'actionActivation' -> object with constructor 'ActionActivationProjection'
        | property 'store' -> object with constructor 'ObjectStoreActionActivationStore'
        --- property 'engine' closes the circle
        

        Studio calls GET /api/v1/packages three times on every open and gets 500 each time (package switcher / publish surfaces read it). The 500 is not logged server-side (filed separately).

        Why

        • ObjectQL.registerApp(manifest) hands the app's defineStack(...) object to SchemaRegistry.installPackage(manifest) (packages/objectql/src/engine.ts ~4795), and installPackage stores that object verbatim as pkg.manifest (packages/objectql/src/registry.ts ~3575-3620).
        • The showcase manifest carries live runtime plugin instances (plugins: [new ConnectorRestPlugin(), new ConnectorMcpPlugin(), MarketplaceProxyPlugin, RuntimeConfigPlugin, ...], examples/app-showcase/objectstack.config.ts ~134-162). After init those instances hold the engine.
        • Since feat(actions): durable packaged-action disable — same activation ledger, dispatch-time consult #12348 (2026-08-25) the engine carries actionActivation -> store -> engine, a reference cycle, so JSON.stringify of anything that reaches the engine throws. Before that PR the same response would have serialized the entire engine graph into the payload instead of failing, which is why this only surfaces now.
        • GET /packages spreads every registry item into the response as-is (packages/rest/src/package-routes.ts ~662-700, { ...item, source: 'registry' }), so one unserializable package item fails the whole list. The Setup package has no plugin instances, hence 200.

        Suggested fix

        1. Store a serializable manifest projection at install time: installPackage should keep the spec-shaped manifest (id, name, version, namespace, type, scope, description, dependencies, declared metadata) and drop runtime-only members such as plugin instances. The kernel keeps the live object; the registry item is a record, not the runtime.
        2. GET /packages / GET /packages/:id should project explicit fields rather than spreading the item, so a future non-serializable member degrades to a missing field instead of a 500.
        3. Pin it: a test that registers a package whose manifest carries an object with a reference cycle and asserts JSON.stringify(registry.getPackage(id)) succeeds, plus a route test on GET /packages over the showcase-shaped stack.

        Repro

        pnpm dev -- --fresh -p 3911 # showcase
        curl -s -c c.txt -H 'Content-Type: application/json' -d '{"email":"admin@objectos.ai","password":"admin123"}' http://localhost:3911/api/v1/auth/sign-in/email
        curl -s -b c.txt http://localhost:3911/api/v1/packages | head -c 400

        Found during the objectui pin-bump dogfood (PR #14295); not caused by that bump.

        Generated by Claude Code

        Activity

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

        Metadata

        Metadata

        Assignees

        Labels

        bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

        Type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          GET /api/v1/packages and /meta/package/<showcase> answer 500 "Converting circular structure to JSON" — the registry stores the live defineStack manifest, plugin instances included #14309

          Description

          @hotlong

          What happens

          On a stock showcase boot (main @ a39b02a, objectui pin 67dadd602a3a, objectstack dev --seed-admin), signed in as the seeded admin:

          GET /api/v1/packages -> 500 INTERNAL_ERROR
          GET /api/v1/packages/com.example.showcase -> 500 INTERNAL_ERROR
          GET /api/v1/meta/package/com.example.showcase -> 500 {"error":"Internal server error"}
          GET /api/v1/meta/package/com.objectstack.setup -> 200
          

          Error body of the first two:

          Converting circular structure to JSON
          --> starting at object with constructor '_ObjectQL'
          | property 'actionActivation' -> object with constructor 'ActionActivationProjection'
          | property 'store' -> object with constructor 'ObjectStoreActionActivationStore'
          --- property 'engine' closes the circle
          

          Studio calls GET /api/v1/packages three times on every open and gets 500 each time (package switcher / publish surfaces read it). The 500 is not logged server-side (filed separately).

          Why

          • ObjectQL.registerApp(manifest) hands the app's defineStack(...) object to SchemaRegistry.installPackage(manifest) (packages/objectql/src/engine.ts ~4795), and installPackage stores that object verbatim as pkg.manifest (packages/objectql/src/registry.ts ~3575-3620).
          • The showcase manifest carries live runtime plugin instances (plugins: [new ConnectorRestPlugin(), new ConnectorMcpPlugin(), MarketplaceProxyPlugin, RuntimeConfigPlugin, ...], examples/app-showcase/objectstack.config.ts ~134-162). After init those instances hold the engine.
          • Since feat(actions): durable packaged-action disable — same activation ledger, dispatch-time consult #12348 (2026-08-25) the engine carries actionActivation -> store -> engine, a reference cycle, so JSON.stringify of anything that reaches the engine throws. Before that PR the same response would have serialized the entire engine graph into the payload instead of failing, which is why this only surfaces now.
          • GET /packages spreads every registry item into the response as-is (packages/rest/src/package-routes.ts ~662-700, { ...item, source: 'registry' }), so one unserializable package item fails the whole list. The Setup package has no plugin instances, hence 200.

          Suggested fix

          1. Store a serializable manifest projection at install time: installPackage should keep the spec-shaped manifest (id, name, version, namespace, type, scope, description, dependencies, declared metadata) and drop runtime-only members such as plugin instances. The kernel keeps the live object; the registry item is a record, not the runtime.
          2. GET /packages / GET /packages/:id should project explicit fields rather than spreading the item, so a future non-serializable member degrades to a missing field instead of a 500.
          3. Pin it: a test that registers a package whose manifest carries an object with a reference cycle and asserts JSON.stringify(registry.getPackage(id)) succeeds, plus a route test on GET /packages over the showcase-shaped stack.

          Repro

          pnpm dev -- --fresh -p 3911 # showcase
          curl -s -c c.txt -H 'Content-Type: application/json' -d '{"email":"admin@objectos.ai","password":"admin123"}' http://localhost:3911/api/v1/auth/sign-in/email
          curl -s -b c.txt http://localhost:3911/api/v1/packages | head -c 400

          Found during the objectui pin-bump dogfood (PR #14295); not caused by that bump.

          Generated by Claude Code

          Activity

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

          Metadata

          Metadata

          Assignees

          Labels

          bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

          Type

          Projects

          No projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            GET /api/v1/packages and /meta/package/<showcase> answer 500 "Converting circular structure to JSON" — the registry stores the live defineStack manifest, plugin instances included #14309

            Description

            @hotlong

            What happens

            On a stock showcase boot (main @ a39b02a, objectui pin 67dadd602a3a, objectstack dev --seed-admin), signed in as the seeded admin:

            GET /api/v1/packages -> 500 INTERNAL_ERROR
            GET /api/v1/packages/com.example.showcase -> 500 INTERNAL_ERROR
            GET /api/v1/meta/package/com.example.showcase -> 500 {"error":"Internal server error"}
            GET /api/v1/meta/package/com.objectstack.setup -> 200
            

            Error body of the first two:

            Converting circular structure to JSON
            --> starting at object with constructor '_ObjectQL'
            | property 'actionActivation' -> object with constructor 'ActionActivationProjection'
            | property 'store' -> object with constructor 'ObjectStoreActionActivationStore'
            --- property 'engine' closes the circle
            

            Studio calls GET /api/v1/packages three times on every open and gets 500 each time (package switcher / publish surfaces read it). The 500 is not logged server-side (filed separately).

            Why

            • ObjectQL.registerApp(manifest) hands the app's defineStack(...) object to SchemaRegistry.installPackage(manifest) (packages/objectql/src/engine.ts ~4795), and installPackage stores that object verbatim as pkg.manifest (packages/objectql/src/registry.ts ~3575-3620).
            • The showcase manifest carries live runtime plugin instances (plugins: [new ConnectorRestPlugin(), new ConnectorMcpPlugin(), MarketplaceProxyPlugin, RuntimeConfigPlugin, ...], examples/app-showcase/objectstack.config.ts ~134-162). After init those instances hold the engine.
            • Since feat(actions): durable packaged-action disable — same activation ledger, dispatch-time consult #12348 (2026-08-25) the engine carries actionActivation -> store -> engine, a reference cycle, so JSON.stringify of anything that reaches the engine throws. Before that PR the same response would have serialized the entire engine graph into the payload instead of failing, which is why this only surfaces now.
            • GET /packages spreads every registry item into the response as-is (packages/rest/src/package-routes.ts ~662-700, { ...item, source: 'registry' }), so one unserializable package item fails the whole list. The Setup package has no plugin instances, hence 200.

            Suggested fix

            1. Store a serializable manifest projection at install time: installPackage should keep the spec-shaped manifest (id, name, version, namespace, type, scope, description, dependencies, declared metadata) and drop runtime-only members such as plugin instances. The kernel keeps the live object; the registry item is a record, not the runtime.
            2. GET /packages / GET /packages/:id should project explicit fields rather than spreading the item, so a future non-serializable member degrades to a missing field instead of a 500.
            3. Pin it: a test that registers a package whose manifest carries an object with a reference cycle and asserts JSON.stringify(registry.getPackage(id)) succeeds, plus a route test on GET /packages over the showcase-shaped stack.

            Repro

            pnpm dev -- --fresh -p 3911 # showcase
            curl -s -c c.txt -H 'Content-Type: application/json' -d '{"email":"admin@objectos.ai","password":"admin123"}' http://localhost:3911/api/v1/auth/sign-in/email
            curl -s -b c.txt http://localhost:3911/api/v1/packages | head -c 400

            Found during the objectui pin-bump dogfood (PR #14295); not caused by that bump.

            Generated by Claude Code

            Activity

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

            Metadata

            Metadata

            Assignees

            Labels

            bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

            Type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              GET /api/v1/packages and /meta/package/<showcase> answer 500 "Converting circular structure to JSON" — the registry stores the live defineStack manifest, plugin instances included #14309

              Description

              @hotlong

              What happens

              On a stock showcase boot (main @ a39b02a, objectui pin 67dadd602a3a, objectstack dev --seed-admin), signed in as the seeded admin:

              GET /api/v1/packages -> 500 INTERNAL_ERROR
              GET /api/v1/packages/com.example.showcase -> 500 INTERNAL_ERROR
              GET /api/v1/meta/package/com.example.showcase -> 500 {"error":"Internal server error"}
              GET /api/v1/meta/package/com.objectstack.setup -> 200
              

              Error body of the first two:

              Converting circular structure to JSON
              --> starting at object with constructor '_ObjectQL'
              | property 'actionActivation' -> object with constructor 'ActionActivationProjection'
              | property 'store' -> object with constructor 'ObjectStoreActionActivationStore'
              --- property 'engine' closes the circle
              

              Studio calls GET /api/v1/packages three times on every open and gets 500 each time (package switcher / publish surfaces read it). The 500 is not logged server-side (filed separately).

              Why

              • ObjectQL.registerApp(manifest) hands the app's defineStack(...) object to SchemaRegistry.installPackage(manifest) (packages/objectql/src/engine.ts ~4795), and installPackage stores that object verbatim as pkg.manifest (packages/objectql/src/registry.ts ~3575-3620).
              • The showcase manifest carries live runtime plugin instances (plugins: [new ConnectorRestPlugin(), new ConnectorMcpPlugin(), MarketplaceProxyPlugin, RuntimeConfigPlugin, ...], examples/app-showcase/objectstack.config.ts ~134-162). After init those instances hold the engine.
              • Since feat(actions): durable packaged-action disable — same activation ledger, dispatch-time consult #12348 (2026-08-25) the engine carries actionActivation -> store -> engine, a reference cycle, so JSON.stringify of anything that reaches the engine throws. Before that PR the same response would have serialized the entire engine graph into the payload instead of failing, which is why this only surfaces now.
              • GET /packages spreads every registry item into the response as-is (packages/rest/src/package-routes.ts ~662-700, { ...item, source: 'registry' }), so one unserializable package item fails the whole list. The Setup package has no plugin instances, hence 200.

              Suggested fix

              1. Store a serializable manifest projection at install time: installPackage should keep the spec-shaped manifest (id, name, version, namespace, type, scope, description, dependencies, declared metadata) and drop runtime-only members such as plugin instances. The kernel keeps the live object; the registry item is a record, not the runtime.
              2. GET /packages / GET /packages/:id should project explicit fields rather than spreading the item, so a future non-serializable member degrades to a missing field instead of a 500.
              3. Pin it: a test that registers a package whose manifest carries an object with a reference cycle and asserts JSON.stringify(registry.getPackage(id)) succeeds, plus a route test on GET /packages over the showcase-shaped stack.

              Repro

              pnpm dev -- --fresh -p 3911 # showcase
              curl -s -c c.txt -H 'Content-Type: application/json' -d '{"email":"admin@objectos.ai","password":"admin123"}' http://localhost:3911/api/v1/auth/sign-in/email
              curl -s -b c.txt http://localhost:3911/api/v1/packages | head -c 400

              Found during the objectui pin-bump dogfood (PR #14295); not caused by that bump.

              Generated by Claude Code

              Activity

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

              Metadata

              Metadata

              Assignees

              Labels

              bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

              Type

              Projects

              No projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                GET /api/v1/packages and /meta/package/<showcase> answer 500 "Converting circular structure to JSON" — the registry stores the live defineStack manifest, plugin instances included #14309

                Description

                @hotlong

                What happens

                On a stock showcase boot (main @ a39b02a, objectui pin 67dadd602a3a, objectstack dev --seed-admin), signed in as the seeded admin:

                GET /api/v1/packages -> 500 INTERNAL_ERROR
                GET /api/v1/packages/com.example.showcase -> 500 INTERNAL_ERROR
                GET /api/v1/meta/package/com.example.showcase -> 500 {"error":"Internal server error"}
                GET /api/v1/meta/package/com.objectstack.setup -> 200
                

                Error body of the first two:

                Converting circular structure to JSON
                --> starting at object with constructor '_ObjectQL'
                | property 'actionActivation' -> object with constructor 'ActionActivationProjection'
                | property 'store' -> object with constructor 'ObjectStoreActionActivationStore'
                --- property 'engine' closes the circle
                

                Studio calls GET /api/v1/packages three times on every open and gets 500 each time (package switcher / publish surfaces read it). The 500 is not logged server-side (filed separately).

                Why

                • ObjectQL.registerApp(manifest) hands the app's defineStack(...) object to SchemaRegistry.installPackage(manifest) (packages/objectql/src/engine.ts ~4795), and installPackage stores that object verbatim as pkg.manifest (packages/objectql/src/registry.ts ~3575-3620).
                • The showcase manifest carries live runtime plugin instances (plugins: [new ConnectorRestPlugin(), new ConnectorMcpPlugin(), MarketplaceProxyPlugin, RuntimeConfigPlugin, ...], examples/app-showcase/objectstack.config.ts ~134-162). After init those instances hold the engine.
                • Since feat(actions): durable packaged-action disable — same activation ledger, dispatch-time consult #12348 (2026-08-25) the engine carries actionActivation -> store -> engine, a reference cycle, so JSON.stringify of anything that reaches the engine throws. Before that PR the same response would have serialized the entire engine graph into the payload instead of failing, which is why this only surfaces now.
                • GET /packages spreads every registry item into the response as-is (packages/rest/src/package-routes.ts ~662-700, { ...item, source: 'registry' }), so one unserializable package item fails the whole list. The Setup package has no plugin instances, hence 200.

                Suggested fix

                1. Store a serializable manifest projection at install time: installPackage should keep the spec-shaped manifest (id, name, version, namespace, type, scope, description, dependencies, declared metadata) and drop runtime-only members such as plugin instances. The kernel keeps the live object; the registry item is a record, not the runtime.
                2. GET /packages / GET /packages/:id should project explicit fields rather than spreading the item, so a future non-serializable member degrades to a missing field instead of a 500.
                3. Pin it: a test that registers a package whose manifest carries an object with a reference cycle and asserts JSON.stringify(registry.getPackage(id)) succeeds, plus a route test on GET /packages over the showcase-shaped stack.

                Repro

                pnpm dev -- --fresh -p 3911 # showcase
                curl -s -c c.txt -H 'Content-Type: application/json' -d '{"email":"admin@objectos.ai","password":"admin123"}' http://localhost:3911/api/v1/auth/sign-in/email
                curl -s -b c.txt http://localhost:3911/api/v1/packages | head -c 400

                Found during the objectui pin-bump dogfood (PR #14295); not caused by that bump.

                Generated by Claude Code

                Activity

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

                Metadata

                Metadata

                Assignees

                Labels

                bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

                Type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions