Skip to content

chore(release): promote staging to main for v14.0.27#216

Merged
joshishiv4 merged 271 commits into
mainfrom
staging
Jun 9, 2026
Merged

chore(release): promote staging to main for v14.0.27#216
joshishiv4 merged 271 commits into
mainfrom
staging

Conversation

@parth0025

Copy link
Copy Markdown
Collaborator

Pull Request Template Chooser

Please click the link that matches your contribution type to load the correct format.

Note: Clicking a link will reload this page and clear any text you've already typed here.

  • Bug Fix
    Use this for fixing broken logic or UI glitches.

  • New Feature
    Use this for adding new functionality or components.

  • Refactor
    Use this for code cleanup, performance tweaks, or technical debt.


General Summary

If you don't want to use a specific template, please provide a brief summary of your changes below.

shyamvadaliya12 and others added 30 commits May 11, 2026 16:54
Merge code
…phase1

πŸ”§ fix: Phase 1 - Standardize naming conventions (16 HIGH severity issues)
`verifyJWTTokenWithC` and `verifyJWTTokenWithCV2` were passing
`new RegExp(companyId)` β€” where `companyId` comes straight from the
`companyid` request header β€” as the `audience` option to `jwt.verify`.
A header value like `.*` matched any company in the token audience,
allowing cross-tenant access; the same input doubled as a ReDoS vector.

Replace the regex with two explicit checks:

  1. Reject any `companyid` that isn't a 24-char Mongo ObjectId
     (defense-in-depth against control chars / regex metacharacters).
  2. Verify the JWT without an `audience` option, then run a strict
     membership check (`isCompanyInAudience`) against the comma-joined
     `aud` claim β€” exact match per entry, trim-tolerant.

Response shapes and HTTP codes are unchanged, so existing clients see
no behavioural diff on legitimate requests. Cross-tenant headers now
return 401 instead of leaking data.

Closes #55
…002 / #56)

`app.use(cors({origin: '*'}))` let every browser origin call every API
route β€” combined with credentialed cookies and JS-readable tokens this
opened a CSRF / token-theft path from any third-party origin.

Introduce `utils/cors.js` with three helpers:
  - `buildCorsAllowList(env)` β€” derives the allow-list from WEBURL,
    APIURL, and an optional CORS_ORIGINS comma-list.
  - `isOriginAllowed(origin, list)` β€” exact-match check, trim/trailing-
    slash tolerant, and permissive for no-Origin / `null` / `file://`
    cases so curl, native mobile clients, and the Electron desktop
    build keep working.
  - `corsOriginDelegate` β€” drop-in `origin:` callback for the `cors`
    middleware.

`index.js` now wires that delegate into `app.use(cors({...}))`, and
`.env.example` documents the new optional `CORS_ORIGINS` variable for
multi-domain deployments.

The helper is intentionally generic so the Socket.io fix (BUG-003)
can reuse it.

Closes #56
…UG-003 / #57)

The Socket.io server was instantiated with `cors: {origin: '*', credentials: true}`,
the configuration browsers reject when sane β€” but engine.io accepts it. Any
malicious origin could open an authenticated websocket on behalf of a
logged-in victim, receive their realtime task/comment stream, and emit
events as them.

Reuse the shared CORS allow-list helper at `utils/cors.js` (same module
introduced for the Express HTTP fix in BUG-002 / #56) and wire it into
Socket.io's `cors.origin` callback. `credentials: true` is kept β€” it is
safe now that origins are explicitly checked.

`utils/cors.js` and the `CORS_ORIGINS` entry in `.env.example` are
duplicated with the BUG-002 PR (#103) on purpose: each fix branches from
`staging` independently per the rollout plan, and git merges identical
blobs as a no-op so order of landing does not matter.

Closes #57
…-004 / #58)

The Socket.io admin UI was instantiated with hardcoded credentials:

  instrument(io, {
    auth: { type: "basic", username: "alian", password: "$2a$12$HHe..." },
    namespaceName: "/admin",
    mode: "development"
  });

Anyone with read access to the repo could:
  - reuse the bcrypt hash, or compute the cleartext offline,
  - reach the deployed `/admin` namespace,
  - read connected sockets / rooms / events and emit events as any user.

`mode: "development"` further disabled the TLS-only enforcement on the
admin namespace in production deployments.

Replace with a small env-driven helper `getAdminUiConfig(env)`:

  - Requires BOTH `SOCKETIO_ADMIN_USERNAME` and `SOCKETIO_ADMIN_PASSWORD_HASH`
    to be set (non-empty, non-whitespace). If either is missing the
    helper returns null and `instrument()` is skipped β€” the admin UI is
    disabled entirely. This is the new default for fresh installs and
    for any deployment that doesn't explicitly opt in.
  - `mode` follows `NODE_ENV`: `production` enables TLS-only protection
    on the admin namespace; everything else (including unrecognised
    values) stays `development` to fail closed.

`socket/socketinit.js` exports the helper so the regression suite at
`.claude/tests/test-bug-004.js` can exercise it directly. `.env.example`
documents the new variables and how to generate a bcrypt hash.

Closes #58
…(BUG-005 / #59)

The `/api/v2/sendForgotPasswordEmail` flow built reset tokens as

    let temp = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    let token = '';
    for (let i = 0; i < 8; i++) {
        token += temp.charAt(Math.floor(Math.random() * temp.length));
    }

β€” 8 chars from a 62-char alphabet (~48 bits) drawn from `Math.random()`,
which is neither cryptographically secure nor large enough to resist
brute force against the reset endpoint.

Replace with a single helper:

    exports.generateResetToken = () =>
        crypto.randomBytes(32).toString('hex');

That's 32 bytes (256 bits) of CSPRNG output, hex-encoded to 64 URL-safe
characters. The reset link format, DB shape, and call sites are
unchanged β€” only the token-generation expression is swapped.

The other forgot-password route (`/api/v2/auth/forgot-password` β†’
`exports.sendForgotPassword`) already used a JWT and is unaffected.

Closes #59
…07 / #61)

`GET /api/v1/checkAvaibility` was registered in `Modules/auth/routes.js:9`
and handled by `exports.checkAvaibility` in `Modules/auth/controller.js:109`.
The handler did a `fs.readFileSync('./utils/licensesValidate.js')` and
returned the file's source in the JSON body β€” with no authentication and
no rate limit. The file does not even exist in the repository, and `git
grep` shows zero callers under `Modules/`, `utils/`, or `frontend/src/`,
so this is pure dead code that doubled as a source-disclosure / LFI
attack surface (today returns ENOENT; on any deployment that adds the
file it would dump licensing logic verbatim).

Delete the route, the controller function, and the now-unused
`const fs = require("fs")` import. No backward-compat concerns.

Closes #61
… (BUG-009 / #63)

`Modules/createProject/controller.js:104` had `reject(error)` in the
outer `catch` block of `createProjectFun`. The function is declared as
`async (req, res) => { ... }`, not a `new Promise` constructor body, so
`reject` was never defined in that scope.

Any synchronous throw inside the surrounding `try { … }` (most easily
triggered by a malformed request body that breaks `checkProjectPlan`'s
synchronous prelude) hit that catch, threw a `ReferenceError`, masked
the original error, and left the response hanging until the client or
upstream proxy timed out β€” usually 30–60s.

Replace `reject(error)` with the same response shape the rest of the
function already uses, and log the underlying error via Winston so the
real cause is captured:

    logger.error(`createProjectFun error: ${error.message || error}`);
    res.status(400).send({
        status: false,
        statusText: error.message || error,
    });

Out of scope here: BUG-010 (#64) β€” the `if (data.status) { … }` branch
without an `else` that causes the same handler to hang when
`checkProjectPlan` resolves with `{status: false}`. Tracked separately.

Closes #63
`Modules/createProject/controller.js:83-93` had an `if (data.status) { … }`
on the resolution of `exports.checkProjectPlan(req)` with no `else`
branch. Today every failure path of `checkProjectPlan` happens to go
through `reject`, so the missing branch isn't currently reachable β€” but
the code is one refactor away from a silent hang:

  - If anyone ever moves a failure path from `reject({status:false})`
    to `resolve({status:false})` (a common Promise refactor), the
    handler falls through with no response and the request hangs until
    the upstream proxy times out.
  - `checkProjectPlan` increments `projectCount.projectCount` (and one
    of `publicCount`/`privateCount`) on the company document *before*
    it validates plan limits. Any future failure path that lands here
    via `resolve` would leave that speculative increment in place.

Add a defensive `else` branch that mirrors the existing `.catch`
rollback logic:

    } else {
        exports.removeProjectCount(req.body.CompanyId, req.body.isPrivateSpace);
        res.status(400).send({
            status: false,
            statusText: (data && data.statusText) || 'Project plan check did not pass.',
        });
    }

The branch is reachable today only via test-time stubs (the regression
test exercises it) but closes the latent hang for any future change to
the validation contract.

Sibling fix BUG-009 (#63) β€” `reject(error)` in the outer `catch` β€” is
its own PR.

Closes #64
…ce-bypass

fix(security): reject regex audience in JWT verify (BUG-001 / #55)
…list

fix(security): replace wildcard CORS with env-driven allow-list (BUG-002 / #56)
fix(security): replace wildcard Socket.io CORS with env allow-list (BUG-003 / #57)
…dmin-creds

fix(security): move Socket.io admin UI creds to env, default-off (BUG-004 / #58)
…n-entropy

fix(security): generate password-reset token with crypto.randomBytes (BUG-005 / #59)
…ckavaibility

fix(security): remove unauthenticated file-disclosure endpoint (BUG-007 / #61)
…side-promise

fix(stability): respond instead of ReferenceError in createProjectFun (BUG-009 / #63)
…ect-missing-else

fix(stability): add missing else in createProjectFun (BUG-010 / #64)
…-011 / #65)

`Modules/auth/controller/verifyInvitation.js:26-42` decoded the base64
invitation blob from `req.body.id`, parsed it `&`/`=`-style, and then:

    req.body = finalObj;

That replaced `req.body` with whatever key/value pairs the attacker
chose to encode β€” no allow-list, no shape validation, and any later
code reading `req.body.<anything>` would see attacker input as if it
had been validated. Combined with predictable invitation tokens (the
broader BUG-005 / #59 family) it was a parameter-injection primitive.

Introduce a small exported pure helper:

    exports.parseInviteBlob = (encoded) => { … }

It accepts only the keys actually consumed downstream β€” `userId`,
`companyId`, `linkId`, `docId` β€” and returns either a fresh local
object containing just those keys or `null` for invalid input. It
also:

  - Validates the input alphabet with a strict base64 regex before
    invoking `atob` (the npm `atob@2.1.2` package is permissive and
    happily returns garbage on malformed input).
  - Splits `key=value` on the FIRST `=` only, so values that legally
    contain `=` aren't truncated.
  - Skips parts without `=`.
  - Returns `null` if the blob contains none of the allow-listed keys.

`exports.checkPermission` now reads from a local `invite` object
returned by `parseInviteBlob` and `req.body` is left untouched.

Closes #65
`Modules/auth/helper.js:180-194` hard-coded the auth rate-limit numbers
inside `manageResetAttempt`:

  - 9 failed attempts allowed inside a window,
  - window labelled `fiveMinutes` but actually 10 min,
  - 10-minute block once tripped.

That left every login / forgot-password / reset-password endpoint at
roughly 9 attempts per IP per 10 minutes β€” permissive enough that a
patient distributed-credential-stuffing attacker could grind through
indefinitely. The variable name `fiveMinutes` also disagreed with its
value, which is a maintenance trap.

Tighten the defaults and lift the knobs out to env:

  AUTH_RATE_LIMIT_MAX_ATTEMPTS   default 5  (was 9)
  AUTH_RATE_LIMIT_WINDOW_MS      default 15 min
  AUTH_RATE_LIMIT_BLOCK_MS       default 30 min (was 10)

A new exported helper `getRateLimitConfig()` reads from `process.env`,
falls back to safe defaults on missing / non-numeric / zero input, and
clamps to sane minimums (>=1 attempt, >=1s window/block). It's read at
request time so an operator can adjust without restarting.

`.env.example` documents the three new variables; operators who need
laxer behaviour for staging / load testing can raise them.

The bad `fiveMinutes` variable name is gone; the threshold check now
uses `attempts >= MAX_ATTEMPTS` so the configured limit means what it
says.

Closes #66
…G-013 / #67)

The JWT audience claim is frozen at login and lasts JWT_EXP (24h
default). A user removed from a company between login and token
expiry kept access until the token expired β€” for up to a day on
defaults.

Add a live membership re-check that runs after the existing audience
verification inside `verifyJWTTokenWithC` and `verifyJWTTokenWithCV2`:

  verifyCompanyMembership(uid, companyId)
    1. Validate uid/companyId look like ObjectIds (cheap rejection
       of garbage so we don't waste a Mongo round-trip).
    2. Check the in-memory cache (`node-cache`) β€” separate positive
       and negative entries.
    3. Fall back to `users.findOne({_id, AssignCompany})` and cache
       the boolean for `MEMBERSHIP_CACHE_TTL_SECONDS` (default 60s).
  invalidateMembershipCache(uid, companyId?)
    - Exported so the membership-change flow (add / remove member)
      can wipe the cache and surface changes immediately.

When the check fails, the middleware now returns HTTP 403 with
`isLogout: true` so the frontend logs the user out rather than
silently retrying with a token that the server no longer trusts.

Both middlewares are now `async`. The membership cost is one Mongo
lookup per (user, company) per minute by default β€” negligible next
to the existing JWT verify on the same path.

Doc: `MEMBERSHIP_CACHE_TTL_SECONDS` added to `.env.example` so
operators can dial it down (faster removal propagation) or up
(less Mongo traffic).

Closes #67
…-014 / #68)

`Modules/auth/controller/verifyEmail.js` had three bugs that combined
into an exploitable verification bypass:

  1. Input validation used `!req.body.token`, which lets falsy non-empty
     non-strings through. `![]` is `false`, so `{ token: [] }` passed.
  2. The success branch used `==` (loose equality):
        response.verificationToken == req.body.token
     `"" == []` is `true` under JavaScript's loose-equality rules. The
     codebase stores `verificationToken: ""` both after a successful
     verification and as a fresh-account default, so any account in
     that state could be re-verified by sending `{ uid, token: [] }`.
  3. There was no fallback `else`, so unmatched states (e.g. stored
     token was `null` or `undefined`) silently fell off the end and
     left the response hanging until the proxy timed out.

Also: the expiry check called
     new Date(verificationTokenTime).setMinutes(...)
on potentially-missing fields. `new Date(null)` is the epoch and
`new Date(undefined)` is `Invalid Date`. Either way the resulting
`ValidTime < new Date()` comparison silently bypassed the expiry for
documents missing `verificationTokenTime`.

The rewrite:
  - Validates `uid` and `token` with `typeof x === 'string' && x.length`
    so arrays / objects / numbers / null are rejected up front.
  - Validates `response.verificationToken` is a non-empty string before
    comparing β€” empty / null / undefined stored token β†’ "expired".
  - Treats missing or invalid `verificationTokenTime` as expired rather
    than silently bypassing the 10-minute window.
  - Uses strict equality `===` for the token comparison.
  - Sends exactly one response in every branch so the request never
    hangs.

Closes #68
…016 / #70)

`Modules/tasks/helpers/mongo_helper.js` had two sites where `.catch`
handlers called `reject(error)` AFTER the outer Promise had already
been settled by an earlier `resolve(...)`. Calling `reject` on an
already-settled Promise is a no-op, so the underlying history /
inner-update failures were silently swallowed.

Site 1 (the explicit bug-report site, lines 80-99 of `HandleTask`):

  MongoDbCrudOpration(...).then((response) => {
      socketEmitter.emit(...);
      resolve({status: true, ...});                  // ← settles here
      exports.HandleHistory('task', ...)
          .catch((error) => { reject(error); });     // ← no-op, log lost
      exports.HandleHistory('project', ...)
          .catch((error) => { reject(error); });     // ← no-op, log lost
  })

Refactor to:

  MongoDbCrudOpration(...).then(async (response) => {
      socketEmitter.emit(...);
      await Promise.allSettled([
          exports.HandleHistory('task', ...).catch(log),
          exports.HandleHistory('project', ...).catch(log),
      ]);
      resolve({status: true, ...});
  })

`Promise.allSettled` makes a single history failure non-fatal (the
batch continues) and each per-task `.catch` logs via `logger.error`
so the failure is now visible.

Site 2 (the sibling instance at `convertToSubTaskFunction:335-337`):

  MongoDbCrudOpration(...).then((result) => {
      ...lots of code, including resolve(...)...
  }).catch((error) => { reject(error); })            // ← also no-op

The inner `.catch` here is reachable only after the inner `resolve(...)`
has already settled the outer Promise (Mongo settles the inner before
the .catch fires, and resolve happens inside the .then). Replace
`reject(error)` with `logger.error(...)` so the failure is logged
instead of silently dropped.

Closes #70
…17 / #71)

`array.forEach(async x => { … })` doesn't wait for the callbacks and
silently swallows any rejection. The audit flagged ~14 sites across the
codebase. After reading each in context they split into two categories:

CATEGORY B β€” real concurrency bug (the callback actually had an `await`
inside, so the loop "completed" before the awaited work did):

  - Modules/AI/controller.js:387 β€” `await limitCountUpdate(...)` was
    fire-and-forget, so per-chunk quota updates raced the outer resolve.
    Replace with `for…of` + `await` (serial; at most one chunk has
    `usage` per response so serial is the natural shape).

  - Modules/storage/server/helpers/bucket.helper.js:195 β€” `await
    copyFile(...)` was fire-and-forget, so `resolve()` ran before any
    file had finished copying. Replace with
    `await Promise.allSettled(imageArray.map(async ...))` so copies
    run in parallel and a single failure doesn't abort the batch.

  Also fix a latent secondary bug uncovered during the test:
  `copyFile` itself did `await fs.cp(source, dest, callback)`. `fs.cp`
  is callback-style and returns `undefined`, so `await` resolved
  immediately and the function returned before the copy started β€” the
  surrounding `Promise.allSettled` fix would have been toothless
  without this. Promisify the callback so awaits up the chain actually
  wait.

CATEGORY A β€” the `async` keyword was unused (no `await` inside, callback
was sync). The keyword was misleading but harmless. Drop it so the
pattern is consistent everywhere:

  - Modules/AI/controller.js:395  (string concat)
  - Modules/projectSetting/controller.js:57, 154, 158  (uses .then chain)
  - frontend/src/store/ProjectData/actions.js:206, 261  (Vuex commit)
  - frontend/src/components/molecules/TaskAudioFiles/TaskAudioFiles.vue
    :351, :419, :424  (object construction)
  - frontend/src/components/organisms/HourlyMilestone/helper.js:194
  - frontend/src/components/templates/CreateProject/TaskStatusForm.vue
    :317
  - frontend/src/components/templates/CreateProject/ProjectTaskTypeForm.vue
    :266

Frontend build verified clean (`npm run build`). No remaining
`forEach(async ...)` patterns in executable code (only in fix-comments
that reference the old pattern).

Closes #71
The audit description for BUG-018 β€” `Promise.allSettled rejection
branch never responds, outer Promise stays pending` β€” turned out to be
inaccurate in two ways once verified against the source:

  1. The `else` branch on `if (rejected.length === 0)` already existed
     and called `reject({status: false, statusText: 'error in
     createProject'})`. The outer Promise did NOT hang.
  2. `Promise.allSettled` never rejects in the first place β€” it always
     resolves with the per-promise outcomes. So even without an else,
     the surrounding `.then` would still fire.

What WAS broken (and is fixed here) is the loss of debugging info: the
existing else body was a generic statusText, with every per-query
`reason` in the `rejected` array discarded. The caller and the log
learnt nothing about WHICH prerequisite query failed or WHY.

Replace the else's reject body with a structured payload that includes:

  - `rejectedCount` / `totalCount`
  - `reasons`: array of `{index, reason}` carrying the actual error
    message for each failed prerequisite query.

…and log the same array via `logger.error` so the failure is also in
the server log for ops.

Closes #72
`Modules/MainChats/controller.js:71` did:

    setChat = await MongoDbCrudOpration(req.headers['companyid'], …);

with no `const`/`let`/`var`. That makes `setChat` an implicit global on
the Node process. Two concurrent requests share the same slot and can
clobber each other β€” the second response can include the first
request's data. (Under strict mode the assignment throws
`ReferenceError`; the file isn't strict today, so the live behaviour is
the silent cross-request leak.)

Trivial fix: add `const`. The variable is only used on the very next
line, so block scope is the right shape.

Closes #73
`Modules/MainChats/controller.js` had a cache-key mismatch between
writer and invalidator:

  - `getChats` set the cache at
        `mainChat:${req.headers['companyid']}`
  - `updateMainChat` tried to invalidate at
        `mainChat:${JSON.parse(JSON.stringify(result))?._id}`
    β€” i.e. the updated document's `_id`, NOT the companyId.

The two keys never matched. `removeCache(...)` was a silent no-op and
stale chats stayed cached until the 3600s TTL expired.

Centralise the key in a small helper so both sites use the same shape:

    const mainChatCacheKey = (companyId) => `mainChat:${companyId}`;

…and use it for both `myCache.set` and `removeCache`. The helper is
exported so the regression test can pin the shape.

Closes #74
parth0025 and others added 19 commits June 9, 2026 12:04
Removes the backend npm ci, npm test, and npm audit steps from the validate job. Backend deps still install on the VPS at deploy time; validate is now a frontend build + artifact job.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add release-please-config.json β€” tracks root package only,
  SemVer bump rules, CHANGELOG sections (user-facing types visible,
  engineering types hidden)
- Add .release-please-manifest.json β€” anchors current version at
  14.0.26 (matches existing v14.0.26 tag)
- Add .github/workflows/release.yml β€” runs on push to main and
  workflow_dispatch; uses googleapis/release-please-action@v4
- Add CHANGELOG.md β€” bootstrapped with v14.0.26 baseline;
  release-please appends new sections from here

Step 4 of the open-source repo maintenance baseline initiative.

How it works: when commits land on main (via the staging promotion
PR from BRANCHING.md), release-please opens a Release PR with the
version bump and grouped Conventional-Commits changelog. Merging
that PR creates the git tag, GitHub Release, and commits the bumps.

Frontend versioning (8.36.0) stays independent for now; can be added
as a second release-please component later if a unified version is
desired.

Zero new npm dependencies β€” the action runs entirely on GitHub
runners.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rewrite Dockerfile as a multi-stage build on node:20-alpine
  (~150MB vs the previous ~1GB), npm ci for reproducibility,
  non-root user, tini for signal handling, HEALTHCHECK via Node
- Drop broken admin/ references from the old Dockerfile (admin
  module is removed from the open-source repo)
- Rewrite docker-compose.yml as a complete stack: MongoDB service,
  persistent volumes (mongo data + app uploads), env_file, health
  checks, depends_on service_healthy gate, restart policies,
  configurable port, dedicated bridge network
- Expand .dockerignore: add .git, .github, .env*, .claude, *.md,
  tests, coverage, logs, IDE/OS files β€” smaller build context and
  prevents accidental secrets leak
- Add .github/workflows/docker.yml: multi-arch builds (amd64+arm64),
  pushes to GHCR on release-published (from release-please in #212)
  and on main pushes, PR builds run image verification only (no
  push). Docker Hub publishing is commented out β€” uncomment after
  adding DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repo secrets.

Step 5 of the open-source repo maintenance baseline initiative.

After this lands and the first release-please release is cut,
end users will be able to:
  docker pull ghcr.io/aliansoftwareteam/alianhub:latest
  docker compose up -d

Image name uses the short branded form (aliansoftwareteam/alianhub)
rather than the auto-derived full repo path.

Zero new npm dependencies β€” the workflow runs entirely on GitHub
runners.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Docker build on PR #213 failed with:
  ERROR: failed to compute cache key:
  failed to calculate checksum of ref ...: "/migrations": not found

Same class of error as the broken admin/ reference in the previous
Dockerfile β€” over-specified folders that don't actually exist at
the repo root.

- Remove COPY locale/        β€” locales live under frontend/src/locales/
- Remove COPY migrations/    β€” migrate-mongo is a dep but no migrations
                                folder exists yet
- Add    COPY socket/        β€” Socket.io runtime files
- Add    COPY public/        β€” Express static assets

All 9 COPY'd folders verified present in the working tree.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The frontend build fails with:
  Module not found: Error: Can't resolve '../../../../../package.json'
  in '/app/frontend/src/components/organisms/Header'

frontend/src/components/organisms/Header/Header.vue imports
{version} from "../../../../../package.json" β€” i.e. the root
package.json, used to render the app version in the UI header.

The frontend-builder stage only copied frontend/package.json into
/app/frontend/. Adding COPY package.json /app/package.json so the
5-level relative import resolves.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Don't run the Docker workflow during routine staging-targeted PR
work. It now triggers only when:
  - A PR targets main (typically the staging->main promotion PR)
  - A commit lands on main
  - A release is published
  - workflow_dispatch (manual)

Trade-off: Docker breakage won't be caught until the staging->main
promotion PR opens. Acceptable since Docker files rarely change in
day-to-day work. Easy to revert by re-adding 'staging' to
pull_request.branches if frequent Docker changes become a pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
README:
- Add 3 GitHub-native badges (release, discussions, stars)
- Add hero screenshot under the badges row
- Convert .gitbook/* image paths to absolute raw URLs so images
  render in non-GitHub viewers (markdownlivepreview, npm, Docker
  Hub, external aggregators)
- New Quick Start with TWO options: Docker (recommended) + From
  source
- New Architecture section with a Mermaid diagram (Vue/Electron
  -> Express+Socket.io -> MongoDB + Wasabi/local storage)
- Expand Key Features from 6 bare bullets to 9 descriptive
  paragraphs covering project mgmt, RBAC + multi-tenancy,
  real-time collab, search + saved filters, timesheets, AI,
  chat, web+desktop, and self-hosting
- New πŸ“Έ Screenshots section with 8 subsections:
  Dashboard / Board (Kanban) / List / Calendar / Task Detail /
  Workload Report / Settings & Customization / AI Assist
- Rename "Getting Started" -> "Documentation" with cleaner
  per-file links
- New Roadmap section pointing to new ROADMAP.md
- Rewrite Contributing section with BRANCHING.md link and
  good-first-issue / help-wanted shortcuts
- New Support & Community section with where-do-I-go matrix
- New Repo Activity section using GitHub-native shields, with
  a Repobeats upgrade-path comment for later

CONTRIBUTING.md:
- New Commit Message Format section explaining Conventional
  Commits, accepted types, and npm run lint:commits

New files:
- ROADMAP.md (75 lines) β€” public roadmap template with
  Recently shipped / In progress / Planned / Considering /
  Out of scope sections
- SUPPORT.md (86 lines) β€” routing matrix (docs, Discussions,
  Issues, Security, Commercial), bug-report content guide,
  response expectations
- .gitbook/assets/screenshots/*.png β€” 8 product screenshots
  (1.2 MB total)

Step 6 of the open-source repo maintenance baseline initiative.

The License section and badge, the Branch Naming Convention in
CONTRIBUTING.md, and the default PR template are intentionally
left untouched to avoid conflicts with PRs #207, #208, and #209.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Step 7 of the open-source repo maintenance baseline initiative β€”
and the final one.

package.json:
- Add description (marketing-grade single-line pitch)
- Add 24 keywords for GitHub repo search and any future npm
  publish (project-management, self-hosted, kanban, gantt,
  multi-tenant, jira-alternative, asana-alternative,
  monday-alternative, etc.)
- Add homepage, repository, bugs metadata fields

New file .claude/DISCOVERABILITY.md (internal, 464 lines):
- Audit of current GitHub repo metadata (description, 17 topics
  already set βœ“)
- 3 high-value topic additions with ready gh repo edit commands
- Submission targets ranked by ROI with ready-to-paste copy for
  awesome-selfhosted, alternativeto.net, Product Hunt,
  Hacker News (Show HN), Reddit (r/selfhosted, r/opensource,
  r/coolgithubprojects), Dev.to + Hashnode launch outline,
  LibHunt, opensource.builders, Indie Hackers
- SEO snippets for alianhub.com:
    Core meta tags (title, description, OG, Twitter Cards)
    schema.org SoftwareApplication JSON-LD markup
    Sitemap & robots.txt reminders
- Tracking checklist with markdown checkboxes for every channel
- Maintenance cadence (once, per release, quarterly, annually)
- "What we deliberately skipped" with reasoning

.claude/REFERENCES.md: added DISCOVERABILITY.md to docs index.

Quality checks applied per code review:
- Meta description shortened from 215 to 140 chars (Google
  SERP truncation fit)
- r/projectmanagement post replaced with SKIP recommendation
  (high mod-removal risk; safer subreddits called out instead)
- HN tactics updated to 2026 reality (30+ upvotes/hour bar)
- og-image.png upgraded to BLOCKER with explicit creation guide

The playbook documents intent only β€” no external submissions or
repo-metadata changes are auto-executed. All gh repo edit commands
are ready to copy-paste after merge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…l-3.0

chore(license): relicense from MIT to AGPL-3.0-or-later
docs(branching): add branching strategy and update CONTRIBUTING
- Remove standalone hotfix checkbox from PR template (not a
  commitlint-accepted type; note in comment explains hotfix
  branches should still use `fix` for their commits/PR title)
- Pin 3 GitHub Actions to commit SHAs (supply-chain hardening):
    amannn/action-semantic-pull-request -> v5.5.3 SHA
    actions/checkout                   -> v4.2.2 SHA
    actions/setup-node                 -> v4.1.0 SHA
- Add persist-credentials: false to checkout step (prevents
  GITHUB_TOKEN from leaking into subsequent steps)
- Match Node version to package.json engines: 22 -> 20
  (eliminates EBADENGINE warnings)
- Make lint:commits work for forks/PRs against different bases:
  hardcoded origin/staging -> ${BASE_BRANCH:-origin/staging}

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fixes CI failure on PR #209:
  npm error EUSAGE
  `npm ci` can only install packages when your package.json and
  package-lock.json are in sync. Missing: @commitlint/cli@^19.5.0,
  @commitlint/config-conventional@^19.5.0 (and ~50 transitive deps).

When the commitlint devDeps were added to package.json in the
initial commit, package-lock.json wasn't regenerated. `npm ci`
runs in strict mode and refused to install packages not in the
lockfile.

Generated via: npm install --package-lock-only

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…s-lint

ci(lint): enforce Conventional Commits and branch-name conventions
ci(release): add release-please automation
docs(readme): polish README + add ROADMAP, SUPPORT, 8 screenshots
docs(discoverability): submission playbook + package.json metadata
ci(docker): publish multi-arch images to GHCR + complete compose stack
@parth0025 parth0025 self-assigned this Jun 9, 2026
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 292 files, which is 142 over the limit of 150.

To get a review, narrow the scope:
β€’ coderabbit review --type committed # exclude uncommitted changes
β€’ coderabbit review --dir # limit to a subdirectory
β€’ coderabbit review --base # compare against a closer base

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 570816c3-4f6d-4064-ab85-b65426878659

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between a9a05c3 and 6d3f34d.

β›” Files ignored due to path filters (8)
  • .gitbook/assets/screenshots/ai-assist.png is excluded by !**/*.png
  • .gitbook/assets/screenshots/board-view.png is excluded by !**/*.png
  • .gitbook/assets/screenshots/calendar-view.png is excluded by !**/*.png
  • .gitbook/assets/screenshots/dashboard.png is excluded by !**/*.png
  • .gitbook/assets/screenshots/list-view.png is excluded by !**/*.png
  • .gitbook/assets/screenshots/settings.png is excluded by !**/*.png
  • .gitbook/assets/screenshots/task-detail.png is excluded by !**/*.png
  • .gitbook/assets/screenshots/workload-report.png is excluded by !**/*.png
πŸ“’ Files selected for processing (292)
  • .claude/AI-TIME-ESTIMATION.md
  • .claude/ANTI-PATTERNS.md
  • .claude/ARCHITECTURE.md
  • .claude/COMMON-TASKS.md
  • .claude/CONVENTIONS.md
  • .claude/DESIGN.md
  • .claude/DISCOVERABILITY.md
  • .claude/EMPLOYEE-WORKLOAD-REPORT-PLAN.md
  • .claude/FOLDER-STRUCTURE.md
  • .claude/MEMORY.md
  • .claude/REFERENCES.md
  • .claude/SECURITY-PATTERNS.md
  • .claude/SETUP.md
  • .claude/SOCKET-PERFORMANCE-PLAN.md
  • .claude/TECH-STACK.md
  • .claude/TEST-CASES.md
  • .claude/archives/qa-reports/auth-module-qa-report.md
  • .claude/archives/qa-reports/repo-structure-audit.md
  • .claude/archives/qa-reports/staging-qa-commits.md
  • .claude/memory/project_key_decisions.md
  • .claude/memory/project_tech_stack.md
  • .claude/memory/reference_key_files.md
  • .claude/settings.local.json
  • .claude/test-cases/ChangePassword.md
  • .claude/test-cases/Company.md
  • .claude/test-cases/CompanySetting.md
  • .claude/test-cases/CustomFieldManager.md
  • .claude/test-cases/Members.md
  • .claude/test-cases/MyProfile.md
  • .claude/test-cases/Notifications.md
  • .claude/test-cases/Project.md
  • .claude/test-cases/ProjectTimesheet.md
  • .claude/test-cases/SecurityPermissions.md
  • .claude/test-cases/SettingsProjects.md
  • .claude/test-cases/Tasks.md
  • .claude/test-cases/Teams.md
  • .claude/test-cases/Templates.md
  • .claude/test-cases/TimeTracking.md
  • .claude/test-cases/TrackerTimesheet.md
  • .claude/test-cases/UserTimesheet.md
  • .claude/test-cases/WorkloadTimesheet.md
  • .claude/tests/bug-011-test-results.txt
  • .claude/tests/bug-012-test-results.txt
  • .claude/tests/bug-013-test-results.txt
  • .claude/tests/bug-014-test-results.txt
  • .claude/tests/bug-015-test-results.txt
  • .claude/tests/bug-016-test-results.txt
  • .claude/tests/bug-017-test-results.txt
  • .claude/tests/bug-018-test-results.txt
  • .claude/tests/bug-019-test-results.txt
  • .claude/tests/bug-020-test-results.txt
  • .claude/tests/bug-021-test-results.txt
  • .claude/tests/bug-022-test-results.txt
  • .claude/tests/bug-023-test-results.txt
  • .claude/tests/bug-024-test-results.txt
  • .claude/tests/bug-025-test-results.txt
  • .claude/tests/bug-032-test-results.txt
  • .claude/tests/bug-033-test-results.txt
  • .claude/tests/bug-034-test-results.txt
  • .claude/tests/bug-035-test-results.txt
  • .claude/tests/bug-036-test-results.txt
  • .claude/tests/bug-037-test-results.txt
  • .claude/tests/bug-038-test-results.txt
  • .claude/tests/bug-039-test-results.txt
  • .claude/tests/bug-040-test-results.txt
  • .claude/tests/bug-041-test-results.txt
  • .claude/tests/bug-042-test-results.txt
  • .claude/tests/bug-043-test-results.txt
  • .claude/tests/bug-044-test-results.txt
  • .claude/tests/bug-045-test-results.txt
  • .claude/tests/bug-046-test-results.txt
  • .claude/tests/bug-047-test-results.txt
  • .claude/tests/smoke-phase2.js
  • .claude/tests/test-bug-011.js
  • .claude/tests/test-bug-012.js
  • .claude/tests/test-bug-013.js
  • .claude/tests/test-bug-014.js
  • .claude/tests/test-bug-015.js
  • .claude/tests/test-bug-016.js
  • .claude/tests/test-bug-017.js
  • .claude/tests/test-bug-018.js
  • .claude/tests/test-bug-019.js
  • .claude/tests/test-bug-020.js
  • .claude/tests/test-bug-021.js
  • .claude/tests/test-bug-022.js
  • .claude/tests/test-bug-023.js
  • .claude/tests/test-bug-024.js
  • .claude/tests/test-bug-025.js
  • .claude/tests/test-bug-026.js
  • .claude/tests/test-bug-032.js
  • .claude/tests/test-bug-033.js
  • .claude/tests/test-bug-034.js
  • .claude/tests/test-bug-035.js
  • .claude/tests/test-bug-036.js
  • .claude/tests/test-bug-037.js
  • .claude/tests/test-bug-038.js
  • .claude/tests/test-bug-039.js
  • .claude/tests/test-bug-040.js
  • .claude/tests/test-bug-041.js
  • .claude/tests/test-bug-042.js
  • .claude/tests/test-bug-043.js
  • .claude/tests/test-bug-044.js
  • .claude/tests/test-bug-046.js
  • .claude/tests/test-bug-047.js
  • .dockerignore
  • .env.example
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/pull_request_template.md
  • .github/workflows/branch-name.yml
  • .github/workflows/commitlint.yml
  • .github/workflows/docker.yml
  • .github/workflows/main.yml
  • .github/workflows/release.yml
  • .gitignore
  • .node-version
  • .release-please-manifest.json
  • BRANCHING.md
  • CHANGELOG.md
  • CLAUDE.md
  • CONTRIBUTING.md
  • COPYRIGHT
  • Config/aws.js
  • Config/config.js
  • Config/firebaseConfig.js
  • Config/jwt.js
  • Config/loggerConfig.js
  • Config/setMiddleware.js
  • Dockerfile
  • LICENSE
  • Modules/AI/controller.js
  • Modules/AIProjectGenerator/briefExtractor.js
  • Modules/AIProjectGenerator/clarifier.js
  • Modules/AIProjectGenerator/controller.js
  • Modules/AIProjectGenerator/init.js
  • Modules/AIProjectGenerator/llmProvider/anthropicProvider.js
  • Modules/AIProjectGenerator/llmProvider/deepseekProvider.js
  • Modules/AIProjectGenerator/llmProvider/index.js
  • Modules/AIProjectGenerator/llmProvider/openaiProvider.js
  • Modules/AIProjectGenerator/llmProvider/types.js
  • Modules/AIProjectGenerator/orchestrator.js
  • Modules/AIProjectGenerator/promptBuilder.js
  • Modules/AIProjectGenerator/prompts/clarify/examples.md
  • Modules/AIProjectGenerator/prompts/clarify/output-schema.md
  • Modules/AIProjectGenerator/prompts/clarify/system.md
  • Modules/AIProjectGenerator/prompts/project-plan/examples.md
  • Modules/AIProjectGenerator/prompts/project-plan/schema.md
  • Modules/AIProjectGenerator/prompts/project-plan/special-sprints-guidance.md
  • Modules/AIProjectGenerator/prompts/project-plan/sprint-guidance.md
  • Modules/AIProjectGenerator/prompts/project-plan/system.md
  • Modules/AIProjectGenerator/prompts/project-plan/task-category.md
  • Modules/AIProjectGenerator/prompts/project-plan/task-guidance.md
  • Modules/AIProjectGenerator/prompts/project-plan/task-time-estimate.md
  • Modules/AIProjectGenerator/prompts/project-plan/workflow-guidance.md
  • Modules/AIProjectGenerator/prompts/shared/brief-handling.md
  • Modules/AIProjectGenerator/prompts/shared/color-rule.md
  • Modules/AIProjectGenerator/prompts/shared/member-rule.md
  • Modules/AIProjectGenerator/prompts/shared/output-format.md
  • Modules/AIProjectGenerator/prompts/shared/role-pm.md
  • Modules/AIProjectGenerator/routes.js
  • Modules/AIProjectGenerator/schemaValidator.js
  • Modules/AIProjectGenerator/sseEmitter.js
  • Modules/Admin/common/controller.js
  • Modules/AdvancedGlobalFilter/controller.js
  • Modules/AdvancedGlobalFilter/init.js
  • Modules/AdvancedGlobalFilter/routes.js
  • Modules/Affiliate/controller.js
  • Modules/Affiliate/init.js
  • Modules/Affiliate/routes.js
  • Modules/Auth/controller.js
  • Modules/Auth/controller/authHelpers.js
  • Modules/Auth/controller/createUser.js
  • Modules/Auth/controller/helper.js
  • Modules/Auth/controller/loginSession.js
  • Modules/Auth/controller/password.js
  • Modules/Auth/controller/register.js
  • Modules/Auth/controller/sendForgotPasswordMail.js
  • Modules/Auth/controller/sendInvitation.js
  • Modules/Auth/controller/sendVerificationMail.js
  • Modules/Auth/controller/verifyEmail.js
  • Modules/Auth/controller/verifyInvitation.js
  • Modules/Auth/helper.js
  • Modules/Auth/init.js
  • Modules/Auth/routes.js
  • Modules/Auth/routes2.js
  • Modules/Auth/session.js
  • Modules/CheckInstallStep/controller.js
  • Modules/CheckInstallStep/createCompany.js
  • Modules/CheckInstallStep/defaultSubscriptionData.js
  • Modules/CheckInstallStep/eventController.js
  • Modules/CheckInstallStep/init.js
  • Modules/CheckInstallStep/initalizations.js
  • Modules/CheckInstallStep/routes.js
  • Modules/Comments/controller.js
  • Modules/Company/controller.js
  • Modules/Company/controller/updateCompany.js
  • Modules/Company/controller2.js
  • Modules/Company/routes.js
  • Modules/CustomField/controller.js
  • Modules/CustomField/init.js
  • Modules/CustomField/routes.js
  • Modules/EmailNotification/init.js
  • Modules/EmailNotification/routes.js
  • Modules/EstimatedTime/aiTaskCategory.js
  • Modules/EstimatedTime/aiTaskEstimator.js
  • Modules/EstimatedTime/aiWorkloadSummary.js
  • Modules/EstimatedTime/controller.js
  • Modules/EstimatedTime/routes.js
  • Modules/ImportSettings/controller.js
  • Modules/ImportSettings/init.js
  • Modules/ImportSettings/routes.js
  • Modules/Invoice/controller.js
  • Modules/LogTime/controllerV2.js
  • Modules/LogTime/controllerV2/capture.js
  • Modules/LogTime/controllerV2/helpers.js
  • Modules/LogTime/controllerV2/manualLogtime.js
  • Modules/LogTime/controllerV2/timelog.js
  • Modules/LogTime/controllerV2/tracker.js
  • Modules/LogTime/init.js
  • Modules/LogTime/routes.js
  • Modules/MainChats/controller.js
  • Modules/MediaFiles/controller.js
  • Modules/Milestone/controller.js
  • Modules/Milestone/controller/crud.js
  • Modules/Milestone/controller/helpers.js
  • Modules/Milestone/controller/query.js
  • Modules/Milestone/controller/status.js
  • Modules/Milestone/init.js
  • Modules/Milestone/routes.js
  • Modules/OAuth/controller.js
  • Modules/OAuth/init.js
  • Modules/OAuth/routes.js
  • Modules/PlanFeature/controller.js
  • Modules/PlanFeature/init.js
  • Modules/PlanFeature/routes.js
  • Modules/Project/controller/getProjectFilterData.js
  • Modules/Project/controller/getSprintFolder.js
  • Modules/ProjectTemplates/controller.js
  • Modules/ScreenshotRetention/controller.js
  • Modules/ScreenshotRetention/helper.js
  • Modules/ScreenshotRetention/init.js
  • Modules/ScreenshotRetention/routes.js
  • Modules/Sprints/controller.js
  • Modules/Sprints/init.js
  • Modules/Sprints/routes.js
  • Modules/SubscriptionPlan/controller.js
  • Modules/Tasks/controller/getTabSyncTasks.js
  • Modules/Tasks/helpers/getTasksData.js
  • Modules/Tasks/helpers/handleNotification.js
  • Modules/Tasks/helpers/helper.js
  • Modules/Tasks/helpers/manageGlobalFilter.js
  • Modules/Tasks/helpers/mongo_helper.js
  • Modules/Tasks/helpers/notificationTemplate.js
  • Modules/Tasks/helpers/reconcileTaskCount.js
  • Modules/Tasks/helpers/taskMongo/bulk.js
  • Modules/Tasks/helpers/taskMongo/create.js
  • Modules/Tasks/helpers/taskMongo/internals.js
  • Modules/Tasks/helpers/taskMongo/mergeDuplicate.js
  • Modules/Tasks/helpers/taskMongo/structural.js
  • Modules/Tasks/helpers/taskMongo/updateAssignment.js
  • Modules/Tasks/helpers/taskMongo/updateBasic.js
  • Modules/Tasks/helpers/taskMongo/updateMeta.js
  • Modules/Tasks/helpers/task_class.js
  • Modules/Tasks/helpers/task_class_Mongo.js
  • Modules/Tasks/init.js
  • Modules/Tasks/routes.js
  • Modules/TimeSheet/controller/getTimeSheetByAggregate.js
  • Modules/UserDashboard/controller.js
  • Modules/UserDashboard/routes.js
  • Modules/Users/controller.js
  • Modules/Users/init.js
  • Modules/Users/routes.js
  • Modules/auth/controller.js
  • Modules/auth/controller/verifyEmail.js
  • Modules/common/controller.js
  • Modules/createProject/controller.js
  • Modules/logTime/controllerV2.js
  • Modules/milestone/controller.js
  • Modules/notification-count/controller.js
  • Modules/notification/app-notification/controller.js
  • Modules/notification/notification-middleware/controllerV2.js
  • Modules/notification/prepare-notification-data/user-controllerV2.js
  • Modules/notification/sendEmail/controller.js
  • Modules/notification/sendEmail/controllerV2.js
  • Modules/notification1/routes.js
  • Modules/projectSetting/controller.js
  • Modules/servicewithAWS.js
  • Modules/storage/server/helpers/bucket.helper.js
  • Modules/storage/wasabi/controller.js
  • Modules/storage/wasabi/routes.js
  • Modules/subscription/controller.js
  • Modules/swaggerAPI/init.js
  • Modules/tasks/helpers/task_class_Mongo.js

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch staging

Comment @coderabbitai help to get the list of available commands and usage tips.

@parth0025 parth0025 requested a review from joshishiv4 June 9, 2026 13:41
@parth0025 parth0025 changed the title Staging chore(release): promote staging to main for v14.0.27 Jun 9, 2026
PR #216 (staging -> main, the first promotion) surfaced two gaps
in our PR-validation workflows:

1. branch-name.yml validates the source branch against a fixed
   list of patterns. `staging` and `main` (used as source branches
   in promotion PRs and hotfix-merge-back flows) were not in the
   allowlist, so PR #216 failed the check.

2. commitlint.yml runs commitlint against every commit in the PR.
   For promotion PRs that aggregate hundreds of legacy commits
   (e.g. PR #216 brings 269 commits, many predating Conventional
   Commits enforcement), the check always fails.

Both fixes are scoped to promotion-PR scenarios β€” they do NOT
weaken validation for normal topic-branch PRs.

branch-name.yml:
- Add LONG_RUNNING='^(staging|main)$' to the allow-set, mirroring
  the documented promotion flow in BRANCHING.md
- Update the help text to surface the staging|main option

commitlint.yml:
- Add `if: github.head_ref != 'staging' && github.head_ref != 'main'`
  to the lint-commits job, skipping per-commit validation when the
  source is a long-running branch
- The PR title check (lint-pr-title) is unchanged β€” it still runs
  on every PR including promotion PRs, since the squash commit on
  merge uses the PR title

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…218)

Docker workflow no longer runs on pull_request events. Triggers
are now scoped to:
  - release: published      (release-please tags)
  - push to main            (after a PR merges)
  - workflow_dispatch       (manual run from Actions UI)

Rationale: multi-arch docker builds (amd64 + arm64 via QEMU
emulation) take 12-18 minutes on cold cache, 3-6 minutes on
warm cache. Running them on every PR (even with the paths-filter
scoping to docker-related files) blocks PR review feedback and
slows the iteration loop.

Trade-off accepted:
  - Docker breakage is now caught one step later: after a PR
    merges to main, not during PR review.
  - Easy to revert with a follow-up PR if a breaking change
    lands on main.
  - release.yml will fail loudly if the post-merge image build
    is broken, so we still get a hard signal before tagging
    a release.

Docker files (Dockerfile, .dockerignore, docker-compose.yml,
docker.yml) change rarely β€” most PRs are unaffected by this
change.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@joshishiv4 joshishiv4 merged commit 7039582 into main Jun 9, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants