Skip to content

openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint #154

Description

@cfdude

Reproduced in this repository, just now

$ openspec validate --archived
✗ change/2026-08-25-conductor-tells-the-truth
✗ 1 incomplete task (114/115 completed)

pm reports the same file as 114/114 · 1 lifecycle, outstandingWork: 0, and the archive gate passes. The epic is archived · delivered.

Both are right about the checkboxes and disagree about what they mean.

The collision

pm ships a <!-- pm:lifecycle --> marker for a task that is bookkeeping about a change's own lifecycle — above all the task that archives the change itself, which is unticked at archive time by construction: it cannot be ticked before the thing that ticks it. outstandingWork()'s own docstring names this exactly — "a guard counting raw checkboxes would refuse every correctly finished change."

openspec validate --archivedis that guard. It counts raw checkboxes and has no knowledge of the marker. That file carries 8 lifecycle-marked tasks.

Why this is a live hazard rather than a curiosity

openspec validate --archived's own help text describes it as "for pre-commit linting." So any pm-managed repository that follows that advice gets a pre-commit hook which fails on every correctly-archived change — permanently, with no action that clears it, because ticking the archive task would be a false record and removing the marker would break pm's own gate.

That is not hypothetical: this repository would hit it today if it wired that command in, and the archive workflow pm documents produces the offending shape every time.

Found while

Investigating #123 (adopt the OpenSpec CLI surface for progress). That investigation concluded the CLI should not be adopted as a progress source, and this is the sharpest single reason: the two tools compute different quantities from the same file, and pm's is the one that makes its archive gate correct.

Options, none obviously right

  • Upstream: OpenSpec could ignore a task line carrying an HTML comment marker, or expose an exclusion mechanism. That is the clean fix and it is not ours to make.
  • pm side: document the incompatibility so nobody wires that command into a pm-managed repo expecting it to pass. Cheap, honest, and does not pretend to fix someone else's lint.
  • Neither: accept that the two disagree and say so where the marker is documented.

Recommend the pm-side documentation now and an upstream report separately — this issue exists so the collision is on the record either way, since the failure mode is confusing precisely when it appears (a green repo suddenly failing a lint on a change that shipped correctly).

Note on the counts

The count arrives only as prose inside a human-readable message ("1 incomplete task (114/115 completed)"), so it cannot be consumed programmatically without parsing English — which is why #123 also could not use it as a progress source even setting the semantics aside.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      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;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint · Issue #154 · cfdude/pm · GitHub
      Skip to content

      openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint #154

      Description

      @cfdude

      Reproduced in this repository, just now

      $ openspec validate --archived
      ✗ change/2026-08-25-conductor-tells-the-truth
      ✗ 1 incomplete task (114/115 completed)
      

      pm reports the same file as 114/114 · 1 lifecycle, outstandingWork: 0, and the archive gate passes. The epic is archived · delivered.

      Both are right about the checkboxes and disagree about what they mean.

      The collision

      pm ships a <!-- pm:lifecycle --> marker for a task that is bookkeeping about a change's own lifecycle — above all the task that archives the change itself, which is unticked at archive time by construction: it cannot be ticked before the thing that ticks it. outstandingWork()'s own docstring names this exactly — "a guard counting raw checkboxes would refuse every correctly finished change."

      openspec validate --archivedis that guard. It counts raw checkboxes and has no knowledge of the marker. That file carries 8 lifecycle-marked tasks.

      Why this is a live hazard rather than a curiosity

      openspec validate --archived's own help text describes it as "for pre-commit linting." So any pm-managed repository that follows that advice gets a pre-commit hook which fails on every correctly-archived change — permanently, with no action that clears it, because ticking the archive task would be a false record and removing the marker would break pm's own gate.

      That is not hypothetical: this repository would hit it today if it wired that command in, and the archive workflow pm documents produces the offending shape every time.

      Found while

      Investigating #123 (adopt the OpenSpec CLI surface for progress). That investigation concluded the CLI should not be adopted as a progress source, and this is the sharpest single reason: the two tools compute different quantities from the same file, and pm's is the one that makes its archive gate correct.

      Options, none obviously right

      • Upstream: OpenSpec could ignore a task line carrying an HTML comment marker, or expose an exclusion mechanism. That is the clean fix and it is not ours to make.
      • pm side: document the incompatibility so nobody wires that command into a pm-managed repo expecting it to pass. Cheap, honest, and does not pretend to fix someone else's lint.
      • Neither: accept that the two disagree and say so where the marker is documented.

      Recommend the pm-side documentation now and an upstream report separately — this issue exists so the collision is on the record either way, since the failure mode is confusing precisely when it appears (a green repo suddenly failing a lint on a change that shipped correctly).

      Note on the counts

      The count arrives only as prose inside a human-readable message ("1 incomplete task (114/115 completed)"), so it cannot be consumed programmatically without parsing English — which is why #123 also could not use it as a progress source even setting the semantics aside.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        bugSomething isn't working

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint · Issue #154 · cfdude/pm · GitHub
          Skip to content

          openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint #154

          Description

          @cfdude

          Reproduced in this repository, just now

          $ openspec validate --archived
          ✗ change/2026-08-25-conductor-tells-the-truth
          ✗ 1 incomplete task (114/115 completed)
          

          pm reports the same file as 114/114 · 1 lifecycle, outstandingWork: 0, and the archive gate passes. The epic is archived · delivered.

          Both are right about the checkboxes and disagree about what they mean.

          The collision

          pm ships a <!-- pm:lifecycle --> marker for a task that is bookkeeping about a change's own lifecycle — above all the task that archives the change itself, which is unticked at archive time by construction: it cannot be ticked before the thing that ticks it. outstandingWork()'s own docstring names this exactly — "a guard counting raw checkboxes would refuse every correctly finished change."

          openspec validate --archivedis that guard. It counts raw checkboxes and has no knowledge of the marker. That file carries 8 lifecycle-marked tasks.

          Why this is a live hazard rather than a curiosity

          openspec validate --archived's own help text describes it as "for pre-commit linting." So any pm-managed repository that follows that advice gets a pre-commit hook which fails on every correctly-archived change — permanently, with no action that clears it, because ticking the archive task would be a false record and removing the marker would break pm's own gate.

          That is not hypothetical: this repository would hit it today if it wired that command in, and the archive workflow pm documents produces the offending shape every time.

          Found while

          Investigating #123 (adopt the OpenSpec CLI surface for progress). That investigation concluded the CLI should not be adopted as a progress source, and this is the sharpest single reason: the two tools compute different quantities from the same file, and pm's is the one that makes its archive gate correct.

          Options, none obviously right

          • Upstream: OpenSpec could ignore a task line carrying an HTML comment marker, or expose an exclusion mechanism. That is the clean fix and it is not ours to make.
          • pm side: document the incompatibility so nobody wires that command into a pm-managed repo expecting it to pass. Cheap, honest, and does not pretend to fix someone else's lint.
          • Neither: accept that the two disagree and say so where the marker is documented.

          Recommend the pm-side documentation now and an upstream report separately — this issue exists so the collision is on the record either way, since the failure mode is confusing precisely when it appears (a green repo suddenly failing a lint on a change that shipped correctly).

          Note on the counts

          The count arrives only as prose inside a human-readable message ("1 incomplete task (114/115 completed)"), so it cannot be consumed programmatically without parsing English — which is why #123 also could not use it as a progress source even setting the semantics aside.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            bugSomething isn't working

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

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

              openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint #154

              Description

              @cfdude

              Reproduced in this repository, just now

              $ openspec validate --archived
              ✗ change/2026-08-25-conductor-tells-the-truth
              ✗ 1 incomplete task (114/115 completed)
              

              pm reports the same file as 114/114 · 1 lifecycle, outstandingWork: 0, and the archive gate passes. The epic is archived · delivered.

              Both are right about the checkboxes and disagree about what they mean.

              The collision

              pm ships a <!-- pm:lifecycle --> marker for a task that is bookkeeping about a change's own lifecycle — above all the task that archives the change itself, which is unticked at archive time by construction: it cannot be ticked before the thing that ticks it. outstandingWork()'s own docstring names this exactly — "a guard counting raw checkboxes would refuse every correctly finished change."

              openspec validate --archivedis that guard. It counts raw checkboxes and has no knowledge of the marker. That file carries 8 lifecycle-marked tasks.

              Why this is a live hazard rather than a curiosity

              openspec validate --archived's own help text describes it as "for pre-commit linting." So any pm-managed repository that follows that advice gets a pre-commit hook which fails on every correctly-archived change — permanently, with no action that clears it, because ticking the archive task would be a false record and removing the marker would break pm's own gate.

              That is not hypothetical: this repository would hit it today if it wired that command in, and the archive workflow pm documents produces the offending shape every time.

              Found while

              Investigating #123 (adopt the OpenSpec CLI surface for progress). That investigation concluded the CLI should not be adopted as a progress source, and this is the sharpest single reason: the two tools compute different quantities from the same file, and pm's is the one that makes its archive gate correct.

              Options, none obviously right

              • Upstream: OpenSpec could ignore a task line carrying an HTML comment marker, or expose an exclusion mechanism. That is the clean fix and it is not ours to make.
              • pm side: document the incompatibility so nobody wires that command into a pm-managed repo expecting it to pass. Cheap, honest, and does not pretend to fix someone else's lint.
              • Neither: accept that the two disagree and say so where the marker is documented.

              Recommend the pm-side documentation now and an upstream report separately — this issue exists so the collision is on the record either way, since the failure mode is confusing precisely when it appears (a green repo suddenly failing a lint on a change that shipped correctly).

              Note on the counts

              The count arrives only as prose inside a human-readable message ("1 incomplete task (114/115 completed)"), so it cannot be consumed programmatically without parsing English — which is why #123 also could not use it as a progress source even setting the semantics aside.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                bugSomething isn't working

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

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

                  openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint #154

                  Description

                  @cfdude

                  Reproduced in this repository, just now

                  $ openspec validate --archived
                  ✗ change/2026-08-25-conductor-tells-the-truth
                  ✗ 1 incomplete task (114/115 completed)
                  

                  pm reports the same file as 114/114 · 1 lifecycle, outstandingWork: 0, and the archive gate passes. The epic is archived · delivered.

                  Both are right about the checkboxes and disagree about what they mean.

                  The collision

                  pm ships a <!-- pm:lifecycle --> marker for a task that is bookkeeping about a change's own lifecycle — above all the task that archives the change itself, which is unticked at archive time by construction: it cannot be ticked before the thing that ticks it. outstandingWork()'s own docstring names this exactly — "a guard counting raw checkboxes would refuse every correctly finished change."

                  openspec validate --archivedis that guard. It counts raw checkboxes and has no knowledge of the marker. That file carries 8 lifecycle-marked tasks.

                  Why this is a live hazard rather than a curiosity

                  openspec validate --archived's own help text describes it as "for pre-commit linting." So any pm-managed repository that follows that advice gets a pre-commit hook which fails on every correctly-archived change — permanently, with no action that clears it, because ticking the archive task would be a false record and removing the marker would break pm's own gate.

                  That is not hypothetical: this repository would hit it today if it wired that command in, and the archive workflow pm documents produces the offending shape every time.

                  Found while

                  Investigating #123 (adopt the OpenSpec CLI surface for progress). That investigation concluded the CLI should not be adopted as a progress source, and this is the sharpest single reason: the two tools compute different quantities from the same file, and pm's is the one that makes its archive gate correct.

                  Options, none obviously right

                  • Upstream: OpenSpec could ignore a task line carrying an HTML comment marker, or expose an exclusion mechanism. That is the clean fix and it is not ours to make.
                  • pm side: document the incompatibility so nobody wires that command into a pm-managed repo expecting it to pass. Cheap, honest, and does not pretend to fix someone else's lint.
                  • Neither: accept that the two disagree and say so where the marker is documented.

                  Recommend the pm-side documentation now and an upstream report separately — this issue exists so the collision is on the record either way, since the failure mode is confusing precisely when it appears (a green repo suddenly failing a lint on a change that shipped correctly).

                  Note on the counts

                  The count arrives only as prose inside a human-readable message ("1 incomplete task (114/115 completed)"), so it cannot be consumed programmatically without parsing English — which is why #123 also could not use it as a progress source even setting the semantics aside.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    bugSomething isn't working

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint · Issue #154 · cfdude/pm · GitHub
                      Skip to content

                      openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint #154

                      Description

                      @cfdude

                      Reproduced in this repository, just now

                      $ openspec validate --archived
                      ✗ change/2026-08-25-conductor-tells-the-truth
                      ✗ 1 incomplete task (114/115 completed)
                      

                      pm reports the same file as 114/114 · 1 lifecycle, outstandingWork: 0, and the archive gate passes. The epic is archived · delivered.

                      Both are right about the checkboxes and disagree about what they mean.

                      The collision

                      pm ships a <!-- pm:lifecycle --> marker for a task that is bookkeeping about a change's own lifecycle — above all the task that archives the change itself, which is unticked at archive time by construction: it cannot be ticked before the thing that ticks it. outstandingWork()'s own docstring names this exactly — "a guard counting raw checkboxes would refuse every correctly finished change."

                      openspec validate --archivedis that guard. It counts raw checkboxes and has no knowledge of the marker. That file carries 8 lifecycle-marked tasks.

                      Why this is a live hazard rather than a curiosity

                      openspec validate --archived's own help text describes it as "for pre-commit linting." So any pm-managed repository that follows that advice gets a pre-commit hook which fails on every correctly-archived change — permanently, with no action that clears it, because ticking the archive task would be a false record and removing the marker would break pm's own gate.

                      That is not hypothetical: this repository would hit it today if it wired that command in, and the archive workflow pm documents produces the offending shape every time.

                      Found while

                      Investigating #123 (adopt the OpenSpec CLI surface for progress). That investigation concluded the CLI should not be adopted as a progress source, and this is the sharpest single reason: the two tools compute different quantities from the same file, and pm's is the one that makes its archive gate correct.

                      Options, none obviously right

                      • Upstream: OpenSpec could ignore a task line carrying an HTML comment marker, or expose an exclusion mechanism. That is the clean fix and it is not ours to make.
                      • pm side: document the incompatibility so nobody wires that command into a pm-managed repo expecting it to pass. Cheap, honest, and does not pretend to fix someone else's lint.
                      • Neither: accept that the two disagree and say so where the marker is documented.

                      Recommend the pm-side documentation now and an upstream report separately — this issue exists so the collision is on the record either way, since the failure mode is confusing precisely when it appears (a green repo suddenly failing a lint on a change that shipped correctly).

                      Note on the counts

                      The count arrives only as prose inside a human-readable message ("1 incomplete task (114/115 completed)"), so it cannot be consumed programmatically without parsing English — which is why #123 also could not use it as a progress source even setting the semantics aside.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        bugSomething isn't working

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint · Issue #154 · cfdude/pm · GitHub
                          Skip to content

                          openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint #154

                          Description

                          @cfdude

                          Reproduced in this repository, just now

                          $ openspec validate --archived
                          ✗ change/2026-08-25-conductor-tells-the-truth
                          ✗ 1 incomplete task (114/115 completed)
                          

                          pm reports the same file as 114/114 · 1 lifecycle, outstandingWork: 0, and the archive gate passes. The epic is archived · delivered.

                          Both are right about the checkboxes and disagree about what they mean.

                          The collision

                          pm ships a <!-- pm:lifecycle --> marker for a task that is bookkeeping about a change's own lifecycle — above all the task that archives the change itself, which is unticked at archive time by construction: it cannot be ticked before the thing that ticks it. outstandingWork()'s own docstring names this exactly — "a guard counting raw checkboxes would refuse every correctly finished change."

                          openspec validate --archivedis that guard. It counts raw checkboxes and has no knowledge of the marker. That file carries 8 lifecycle-marked tasks.

                          Why this is a live hazard rather than a curiosity

                          openspec validate --archived's own help text describes it as "for pre-commit linting." So any pm-managed repository that follows that advice gets a pre-commit hook which fails on every correctly-archived change — permanently, with no action that clears it, because ticking the archive task would be a false record and removing the marker would break pm's own gate.

                          That is not hypothetical: this repository would hit it today if it wired that command in, and the archive workflow pm documents produces the offending shape every time.

                          Found while

                          Investigating #123 (adopt the OpenSpec CLI surface for progress). That investigation concluded the CLI should not be adopted as a progress source, and this is the sharpest single reason: the two tools compute different quantities from the same file, and pm's is the one that makes its archive gate correct.

                          Options, none obviously right

                          • Upstream: OpenSpec could ignore a task line carrying an HTML comment marker, or expose an exclusion mechanism. That is the clean fix and it is not ours to make.
                          • pm side: document the incompatibility so nobody wires that command into a pm-managed repo expecting it to pass. Cheap, honest, and does not pretend to fix someone else's lint.
                          • Neither: accept that the two disagree and say so where the marker is documented.

                          Recommend the pm-side documentation now and an upstream report separately — this issue exists so the collision is on the record either way, since the failure mode is confusing precisely when it appears (a green repo suddenly failing a lint on a change that shipped correctly).

                          Note on the counts

                          The count arrives only as prose inside a human-readable message ("1 incomplete task (114/115 completed)"), so it cannot be consumed programmatically without parsing English — which is why #123 also could not use it as a progress source even setting the semantics aside.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            bugSomething isn't working

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

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

                              openspec validate --archived fails every correctly-archived pm change — the pm:lifecycle marker collides with an upstream lint #154

                              Description

                              @cfdude

                              Reproduced in this repository, just now

                              $ openspec validate --archived
                              ✗ change/2026-08-25-conductor-tells-the-truth
                              ✗ 1 incomplete task (114/115 completed)
                              

                              pm reports the same file as 114/114 · 1 lifecycle, outstandingWork: 0, and the archive gate passes. The epic is archived · delivered.

                              Both are right about the checkboxes and disagree about what they mean.

                              The collision

                              pm ships a <!-- pm:lifecycle --> marker for a task that is bookkeeping about a change's own lifecycle — above all the task that archives the change itself, which is unticked at archive time by construction: it cannot be ticked before the thing that ticks it. outstandingWork()'s own docstring names this exactly — "a guard counting raw checkboxes would refuse every correctly finished change."

                              openspec validate --archivedis that guard. It counts raw checkboxes and has no knowledge of the marker. That file carries 8 lifecycle-marked tasks.

                              Why this is a live hazard rather than a curiosity

                              openspec validate --archived's own help text describes it as "for pre-commit linting." So any pm-managed repository that follows that advice gets a pre-commit hook which fails on every correctly-archived change — permanently, with no action that clears it, because ticking the archive task would be a false record and removing the marker would break pm's own gate.

                              That is not hypothetical: this repository would hit it today if it wired that command in, and the archive workflow pm documents produces the offending shape every time.

                              Found while

                              Investigating #123 (adopt the OpenSpec CLI surface for progress). That investigation concluded the CLI should not be adopted as a progress source, and this is the sharpest single reason: the two tools compute different quantities from the same file, and pm's is the one that makes its archive gate correct.

                              Options, none obviously right

                              • Upstream: OpenSpec could ignore a task line carrying an HTML comment marker, or expose an exclusion mechanism. That is the clean fix and it is not ours to make.
                              • pm side: document the incompatibility so nobody wires that command into a pm-managed repo expecting it to pass. Cheap, honest, and does not pretend to fix someone else's lint.
                              • Neither: accept that the two disagree and say so where the marker is documented.

                              Recommend the pm-side documentation now and an upstream report separately — this issue exists so the collision is on the record either way, since the failure mode is confusing precisely when it appears (a green repo suddenly failing a lint on a change that shipped correctly).

                              Note on the counts

                              The count arrives only as prose inside a human-readable message ("1 incomplete task (114/115 completed)"), so it cannot be consumed programmatically without parsing English — which is why #123 also could not use it as a progress source even setting the semantics aside.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                bugSomething isn't working

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions